0

0

[译]C++17, 语言核心层变化的更多细节

星夢妙者

星夢妙者

发布时间:2025-07-21 11:00:01

|

360人浏览过

|

来源于php中文网

原创

在之前的文章中,我介绍了一些c++++17语言核心层的变化。这次我将详细探讨更多相关的细节,涉及的主题包括:内联变量(inline variables)、模板、auto相关的自动类型推导以及属性(attributes)。

C++标准整体的特性时间线

[译]C++17, 语言核心层变化的更多细节上图列出了C++17的主要特性,而本文将介绍一些不太为人所知的特性。

内联变量(Inline variables)过去我们不将C++代码打包成仅含头文件的程序库(header-only libraries)的一个主要原因是为了正确处理相同的变量引用。C++17引入了内联变量,解决了这个问题!现在你可以声明内联的全局变量和静态变量了,相关的规则限制与内联函数一致。

这意味着:

立即学习C++免费学习笔记(深入)”;

你可以重复定义一个内联变量,但该内联变量必须在使用它的编译单元中可见。一个全局内联变量(即非静态内联变量)必须在每一个编译单元中进行声明,并且该全局内联变量在每一个编译单元中都有相同的内存地址。现在你能直接在头文件中声明(内联)变量并且多次包含它们(包含对应的头文件)了!

class Widget{
  public:
    Widget() = default;
    Widget(int w): width(w), height(getHeight(w)){}
    Widget(int w, int h): width(w), height(h){}
  private:
    int getHeight(int w){ return w*3/4; }
    static inline int width= 640;
    static inline int height= 480;
    static inline bool frame= false;
    static inline bool visible= true;
    ...;
};
inline Widget wVGA;

auto可以根据其初始化表达式自动推导变量类型。在C++17中,auto的这种自动类型推导能力进一步增强了。借助auto,函数模板和(类模板的)构造函数的模板参数可以根据其参数自动进行类型推导(细节介绍),非类型模板参数的类型也可以从参数中自动推导出来。下面我将介绍一下非类型模板参数的自动类型推导。

非类型模板参数(non-type template parameters)的自动类型推导首先要说明一下哪些属于非类型模板参数:它们是nullptr、整型、左值引用、指针以及枚举类型。下面的讲解主要以整型为主。

说了这么多理论,是时候看个示例了:

template 
class MyClass{
  ...;
};
template 
class MyClass {
  ...;
};
MyClass myClass2;     // Primary template for char
MyClass myClass1;   // Partial specialisation for int

第1行代码中,通过将模板参数声明为auto,编译器便可以自动推导非类型模板参数(第1行代码中的N)的类型了,你甚至可以像示例代码中那样(第7和第8行)偏特化该模板(示例代码中为int类型进行了偏特化)。第13行代码的模板会依据原始模板(示例中的第一个模板)进行实例化,而第14行代码的实例化依据的则是偏特化模板版本(示例中的第二个模板)。

一般的类型修饰符也可以用在非类型模板参数上,所以很多时候,你不必非得使用模板偏特化来限制非类型模板参数的类型。

template 
struct S;

上述代码中,p被限制成必须是常量指针类型。

即便在可变参数模板中,非类型模板参数也可以进行自动类型推导。

template 
class VariadicTemplate{
  ...;
};
template 
class TypedVariadicTemplate{
  ...;
};

示例代码中,模板VariadicTemplate(第1行至第5行)可以对任意数量的非类型模板参数进行自动类型推导,而TypeVariadicTemplate模板(第7行至第11行)则仅会自动推导第一个非类型模板参数的类型,其余非类型模板参数的类型都与第一个非类型模板参数的类型相同。

C++17更改了auto结合使用列表初始化的规则。

auto结合使用{}初始化在C++17之前,如果你结合使用auto和列表初始化,你会得到一个std::initializer_list。

Giiso写作机器人
Giiso写作机器人

Giiso写作机器人,让写作更简单

下载
auto initA{1};          // std::initializer_list
auto initB = {2};       // std::initializer_list
auto initC{1, 2};       // std::initializer_list
auto initD = {1, 2};    // std::initializer_list

这个规则很容易记忆,也很容易教授,但在C++17中,这个规则变复杂了(个人感觉这个改动并不好)。

auto initA{1};          // int
auto initB = {2};       // std::initializer_list
auto initC{1, 2};       // error, no single element
auto initD = {1, 2};    // std::initializer_list

现在,使用初始化列表进行赋值依然会得到类型std::initializer_list,但使用初始化列表进行复制构造却只支持单个数值了,得到的类型也不再是std::initializer_list,而是对应的初始化数值类型。

接下来让我们来看一些小而美的特性。

Nested namespaces在C++17中,你可以非常简便地定义嵌套的命名空间。

相比之前的写法:

namespace A {
  namespace B {
    namespace C {
      ...;
    }
  }
}

C++17中的写法要简明很多:

namespace A::B::C {
  ...;
}

C++17新增了三个属性[[fallthrough]]、[[nodiscard]]和[[maybe_unused]]。

新增的三个属性fallthrough、nodiscard、maybe_unused这三个属性都是为了处理编译器警告,下面的例子来自于cppreference.com。

fallthrough[[fallthrough]]可以在switch语句中使用,他必须单独占据一行代码,并且后面需要跟随一个case标签(或者default标签)语句,以此来说明代码从[[fallthrough]]的前一个标签“落下”(继续执行后面标签的逻辑,而不break)是有意为之的,编译器不应该诊断其为警告。

这里有个例子:

void f(int n) {
  void g(), h(), i();
  switch (n) {
    case 1:
    case 2:
      g();
      [[fallthrough]];
    case 3: // no warning on fallthrough
      h();
    case 4: // compiler may warn on fallthrough
      i();
      [[fallthrough]]; // ill-formed, not before a case label
  }
}

代码第9行的[[fallthrough]]属性抑制了编译器的编译警告,但是代码第12行由于缺少[[fallthrough]]属性,编译器便有可能产生告警。第14行代码的[[fallthrough]]声明是病态的,因为其后没有跟随case标签(或者default标签)。

nodiscard[[nodiscard]]属性可以用于函数声明、枚举声明以及类声明中。如果你丢弃了一个声明为[[nodiscard]]的函数的返回值,编译器就会产生一个编译警告。同样地,如果你丢弃了函数中返回的(声明为)[[nodiscard]]枚举或者(声明为)[[nodiscard]]类,编译器同样会给出警告,抑制该类警告的一种方法就是对返回值进行一次void转型操作。

下面的示例中,第6行代码会产生一个编译警告,但在第12行代码中,由于foo函数返回的是引用类型(虽然引用类型本身是[[nodiscard]]属性),所以不会产生编译警告。

struct [[nodiscard]] error_info {};
error_info enable_missile_safety_mode();
void launch_missiles();
void test_missiles() {
  enable_missile_safety_mode(); // compiler may warn on discarding a nodiscard value
  launch_missiles();
}
error_info& foo();
void f1() {
  foo(); // nodiscard type is not returned by value, no warning
}

maybe_unused可以使用[[maybe_unused]]的地方很多:类、typedef、变量、非静态成员变量、函数、枚举类型或者枚举值。[[maybe_unused]]可以抑制编译器对于代码中未使用实体的编译警告。

void f([[maybe_unused]] bool thing1,
       [[maybe_unused]] bool thing2){
  [[maybe_unused]] bool b = thing1;
  assert(b); // in release mode, assert is compiled out
}

release模式下,上面第5行代码在编译时会被移除,但是由于我们之前为b声明了[[maybe_unused]]属性,所以编译器不会产生警告,同样地,虽然代码中也没有使用参数thing2,但是由于thing2也声明了[[maybe_unused]]属性,所以也不会产生编译警告。

相关专题

更多
java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1468

2023.10.24

switch语句用法
switch语句用法

switch语句用法:1、Switch语句只能用于整数类型,枚举类型和String类型,不能用于浮点数类型和布尔类型;2、每个case语句后面必须跟着一个break语句,以防止执行其他case的代码块,没有break语句,将会继续执行下一个case的代码块;3、可以在一个case语句中匹配多个值,使用逗号分隔;4、Switch语句中的default代码块是可选的等等。

534

2023.09.21

Java switch的用法
Java switch的用法

Java中的switch语句用于根据不同的条件执行不同的代码块。想了解更多switch的相关内容,可以阅读本专题下面的文章。

417

2024.03.13

java中break的作用
java中break的作用

本专题整合了java中break的用法教程,阅读专题下面的文章了解更多详细内容。

118

2025.10.15

java break和continue
java break和continue

本专题整合了java break和continue的区别相关内容,阅读专题下面的文章了解更多详细内容。

256

2025.10.24

全局变量怎么定义
全局变量怎么定义

本专题整合了全局变量相关内容,阅读专题下面的文章了解更多详细内容。

78

2025.09.18

python 全局变量
python 全局变量

本专题整合了python中全局变量定义相关教程,阅读专题下面的文章了解更多详细内容。

96

2025.09.18

typedef和define区别
typedef和define区别

typedef和define区别在类型检查、作用范围、可读性、错误处理和内存占用等。本专题为大家提供typedef和define相关的文章、下载、课程内容,供大家免费下载体验。

107

2023.09.26

AO3中文版入口地址大全
AO3中文版入口地址大全

本专题整合了AO3中文版入口地址大全,阅读专题下面的的文章了解更多详细内容。

1

2026.01.21

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 11.2万人学习

Rust 教程
Rust 教程

共28课时 | 4.7万人学习

Kotlin 教程
Kotlin 教程

共23课时 | 2.7万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号