C++ Weekly - Episode 130 脱水版: C++20's init statements
C++20 的初始语句
考虑如下一个数组的例子:
int main()
{
std::array data{1, 2, 3, 4, 5};
auto total = 0;
for (const auto v : data) {
total += v;
}
return total;
}
编译器将会优化这段代码, 直接输出结果 15.
C++20 可以允许我们在 for 循环内部声明一些局部范围的变量, 只在循环内有效. 这使得变量作用路径更加紧凑清晰.
int main()
{
std::array data{1, 2, 3};
auto total = 0;
for (std::size_t position = 0; const auto v : data) {
total += (v * position);
position++;
}
return total;
}
这里的重点是, 像 position 的这种声明可以避免变量生命周期的影响. 看下一个例子:
auto get_data() {
std::array data{1, 2, 3};
return data;
}
int main()
{
auto total = 0;
for (const auto v : get_data()) {
total += v;
}
return total; // 6!!!!
}
计算结果正确, 数组被正确便利计算.
但是, 假如这个数组是从一个函数返回的对象中取得的, 比如:
struct S {
std::array<int, 3> data = {1, 2, 3};
const auto &get_data() {
return data;
}
};
S get_S() {
return S{};
}
int main()
{
auto total = 0;
for (const auto v : get_S().get_data()) {
total += v;
}
return total;
}
此时编译器将返回随机值. 因为 get_S() 返回的是一个临时的对象, 在我们遍历 get_data() 时, 这个临时对象已经不再存在, 所以从栈上随机弹出了三个变量.
这时候, 范围 for 循环的变量定义可以实现正确的计算结果, 例如:
struct S {
std::array<int, 3> data = {1, 2, 3};
const auto &get_data() {
return data;
}
};
S get_S() {
return S{};
}
int main()
{
auto total = 0;
for (auto s = get_S(); const auto v : s.get_data()) {
total += v;
}
return total;
}
Note: 作者说他还需要适应这种写法, 我自己重新试了下这个例子 for (const auto v : get_S().get_data()), 在较新的编译器版本或者正式版本的 C++20 的标准下, 也能返回正确的结果. 当初视频录制的时期, 编译器版本较老, clang 10 左右或之前, C++17 或者 C++2a 下, 有兴趣的可以用老版本的编译器复现一下看看, 有点意思.