C++ Weekly - Episode 130 脱水版: C++20's init statements

113 阅读2分钟

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 下, 有兴趣的可以用老版本的编译器复现一下看看, 有点意思.