C++17 详解 9
本文为 《C++17 in detail》 一书的中文渣中渣译文,不足之处还望指正。
3.2 带初始化语句的 if
和 switch
C++17 提供了新版本的 if
和 switch
语句:
if (init; condition)
和 switch (init; condition)
。
在 init
分区你可以定义一个新变量,然后在 condition
分区校验它。此变量只在 if/else
作用域内可见。
要达到相似的效果,C++17 之前你必须这么写:
1 | { |
请注意,val
在一个独立的作用域内(译注:最外层的一对大括号),如果没有它变量会“泄漏”到封闭作用域。
现在,C++17 里,你可以这么写:
1 | if (auto val = GetValue(); condition(val)) |
val
只在 if
和 else
语句内可见,所以它不会“泄漏”。condition
可以是任意 bool
条件。
这样做有什么用呢?
假设你想要在字符串里进行一些查找:
1 | const std::string myString = "My Hello World Wow"; |
你不得不为(两次) pos
使用不同的名字,或者用一个独立的作用域封闭起来:
1 | { |
新的 if
语句可以只用一行生成额外的作用域:
1 | if (const auto pos = myString.find("Hello"); pos != std::string::npos) |
如前所述,if
中定义的变量在 else
块内同样可见。所以下边的写法也没问题:
1 | if (const auto pos = myString.find("World"); pos != std::string::npos) |
另外,你可以利用结构化绑定(摘自 Herb Sutter 代码):
1 | // 更好的结合: 结构化绑定 + if 初始值 |
扩展:本修改提案:P0305R1。
3.3 内联变量
随着 C++11 非静态成员变量初始化的引入,现在你可以在声明成员变量的同时进行初始化:
1 | class User |
对静态变量(或常量静态)你通常仍然需要在 cpp 文件里进行定义。
C++11 和 constexpr
关键字允许你在同一个地方声明并定义静态变量,但是仅限于常量表达式。
在此之前,只有函数可以被指定为 inline
,现在你可以对变量做同样的事了——在头文件里。
摘自提案 P0385R2:内联变量同内联函数具有相同的语义:它可以在多个编译单元内被一模一样地定义多次,必须在每一个用到的编译单元内定义(译注:即可见),程序行为如同它们是同一个变量一样。
译注:上边的引用说明了两件事:
内联变量的可见性规则。其实是跟内联函数一模一样的——如你在头文件中定义,只需包含此头文件,变量即对所在编译单元可见;如在源文件中定义,需要在所有用到的源文件中分别定义,且要保证定义形式一致。
每个编译单元内都有一份变量的定义——不管是你显式定义或是编译器生成的。所以,每个变量都只是如同(原文为 as if)一个变量,而不是真正意义上的一个变量。我在 VS2019 下验证过,不管是头文件中定义后包含,或者每个源文件中分别定义,各个编译单元内打印出的内联变量的地址都是不同的。
比如:
1 | // inside a header file: |
或者甚至在同一个地方声明和定义:
1 | struct MyClass |
注意,constexpr
变量是隐式内联的,所以没必要写 constexpr inline myVal = 10;
。
内联变量比 constexpr
变量更灵活,因为它不必必须用一个常量表达式初始化。比如,你可以用 rand()
初始化一个内联变量,constexpr
则不行。
内联变量怎么做到简化代码的呢?
A lot of header-only libraries can limit the number of hacks(like using inline functions or templates)
and switch to using inline variables.
译注:不知所云,不敢译……
比如:
1 | class MyClass |
可以被改为:
1 | class MyClass |
C++17 保证 MyClass::seed
在所有编译单元内具有相同值(运行时生成)!
扩展:本修改提案:P0386R2。