C++17 详解 9

本文为 《C++17 in detail》 一书的中文渣中渣译文,不足之处还望指正。

3.2 带初始化语句的 ifswitch

C++17 提供了新版本的 ifswitch 语句:

if (init; condition)switch (init; condition)

init 分区你可以定义一个新变量,然后在 condition 分区校验它。此变量只在 if/else 作用域内可见。

要达到相似的效果,C++17 之前你必须这么写:

1
2
3
4
5
6
7
{
auto val = GetValue();
if (condition(val))
// 条件为真
else
// 条件为假...
}

请注意,val 在一个独立的作用域内(译注:最外层的一对大括号),如果没有它变量会“泄漏”到封闭作用域。

现在,C++17 里,你可以这么写:

1
2
3
4
if (auto val = GetValue(); condition(val))
// 条件为真
else
// 条件为假...

val 只在 ifelse 语句内可见,所以它不会“泄漏”。condition 可以是任意 bool 条件。

这样做有什么用呢?

假设你想要在字符串里进行一些查找:

1
2
3
4
5
6
7
8
9
const std::string myString = "My Hello World Wow";

const auto pos = myString.find("Hello");
if (pos != std::string::npos)
std::cout << pos << " Hello\n";

const auto pos2 = myString.find("World");
if (pos2 != std::string::npos)
std::cout << pos2 << " World\n";

你不得不为(两次) pos 使用不同的名字,或者用一个独立的作用域封闭起来:

1
2
3
4
5
6
7
8
9
10
{
const auto pos = myString.find("Hello");
if (pos != std::string::npos)
std::cout << pos << " Hello\n";
}
{
const auto pos = myString.find("World");
if (pos != std::string::npos)
std::cout << pos << " World\n";
}

新的 if 语句可以只用一行生成额外的作用域:

1
2
3
4
5
if (const auto pos = myString.find("Hello"); pos != std::string::npos)
std::cout << pos << " Hello\n";

if (const auto pos = myString.find("World"); pos != std::string::npos)
std::cout << pos << " World\n";

如前所述,if 中定义的变量在 else 块内同样可见。所以下边的写法也没问题:

1
2
3
4
if (const auto pos = myString.find("World"); pos != std::string::npos)
std::cout << pos << " World\n";
else
std::cout << pos << " not found!!\n";

另外,你可以利用结构化绑定(摘自 Herb Sutter 代码):

1
2
3
4
5
// 更好的结合: 结构化绑定 + if 初始值
if (auto [iter, succeeded] = mymap.insert(value); succeeded) {
use(iter); // ok
// ...
} // iter 和 succeeded 在这里销毁

扩展:本修改提案:P0305R1

3.3 内联变量

随着 C++11 非静态成员变量初始化的引入,现在你可以在声明成员变量的同时进行初始化:

1
2
3
4
5
class User
{
int _age {0};
std::string _name {"unknown"};
};

对静态变量(或常量静态)你通常仍然需要在 cpp 文件里进行定义。

C++11 和 constexpr 关键字允许你在同一个地方声明并定义静态变量,但是仅限于常量表达式。

在此之前,只有函数可以被指定为 inline,现在你可以对变量做同样的事了——在头文件里。

摘自提案 P0385R2:内联变量同内联函数具有相同的语义:它可以在多个编译单元内被一模一样地定义多次,必须在每一个用到的编译单元内定义(译注:即可见),程序行为如同它们是同一个变量一样。

译注:上边的引用说明了两件事:

  1. 内联变量的可见性规则。其实是跟内联函数一模一样的——如你在头文件中定义,只需包含此头文件,变量即对所在编译单元可见;如在源文件中定义,需要在所有用到的源文件中分别定义,且要保证定义形式一致。

  2. 每个编译单元内都有一份变量的定义——不管是你显式定义或是编译器生成的。所以,每个变量都只是如同(原文为 as if)一个变量,而不是真正意义上的一个变量。我在 VS2019 下验证过,不管是头文件中定义后包含,或者每个源文件中分别定义,各个编译单元内打印出的内联变量的地址都是不同的。

比如:

1
2
3
4
5
6
7
8
// inside a header file:
struct MyClass
{
static const int sValue;
};

// later in the same header file:
inline int const MyClass::sValue = 777;

或者甚至在同一个地方声明和定义:

1
2
3
4
struct MyClass
{
inline static const int sValue = 777;
};

注意,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
2
3
4
5
6
7
8
9
class MyClass
{
static inline int Seed(); // static method
};

inline int MyClass::Seed() {
static const int seed = rand();
return seed;
}

可以被改为:

1
2
3
4
class MyClass
{
static inline int seed = rand();
};

C++17 保证 MyClass::seed 在所有编译单元内具有相同值(运行时生成)!

扩展:本修改提案:P0386R2

评论