首页 > 解决方案 > 为什么在使用之前没有初始化 C++ const 模板化向量?

问题描述

更新:问题是为什么下面的代码(MWE)按原样工作,而不是我期望的那样。

为了个人方便,我创建了以下模板化向量 const:

// shorthand for loops, etc.
template <size_t N>
const vector<size_t> range = []() {
    vector<size_t> res(N);
    for (size_t i = 0; i < N; i++) res[i] = i;
    cout << "Created range<" << N << ">: [";
    for (auto x: res) cout << x << ' ';
    cout << ']' << endl;
    return res;
}();

因此,我可以编写更简洁的循环,如下所示:

for (auto i : range<42>) do_something(i);

但是,我意识到(经过一些调试)似乎不能保证所有必需的实例range<N>在使用前都被初始化!这是相当违反直觉的,所以我想如果我做错了什么。

更准确地说,我有以下 MWE:

#include <bits/stdc++.h>
using namespace std;

template <size_t N>
const vector<size_t> range = []() {
    cout << "Initialising range<" << N << ">" << endl;
    vector<size_t> result(N);
    for (size_t i = 0; i < N; i++) result[i] = i;
    return result;
}();

template <size_t K>
class Data {
   private:
    size_t m_code;

   public:
    size_t get_code() const { return m_code; }

    constexpr static size_t cardinality = K + 1;

    explicit Data(size_t code);

    const static vector<Data> elems;
};

template <size_t K>
const vector<Data<K>> Data<K>::elems = []() {
    cout << "Creating Data elements for K=" << K << endl;
    vector<Data<K>> xs;
    for (size_t i : range<Data<K>::cardinality>) xs.push_back(Data<K>(i));
    return xs;
}();

template <size_t K>
Data<K>::Data(size_t code) {
    m_code = code;

    cout << "At the moment, range<" << K << "> is [";
    for (auto k : range<K>)
        cout << k << ' ';  // <<< Shouldn't range<K> be already initialised here?..
    cout << "] (len=" << range<K>.size() << ")" << endl;
}

int main() {
    cout << ">>> Inside main()" << endl;
    constexpr size_t K = 2;

    cout << "Data elements:" << endl;
    for (const auto &X : Data<K>::elems) {
        cout << "Element Data(" << X.get_code() << ")" << endl;
    }

    cout << "Now, range<" << K << "> is [";
    for (auto k : range<K>) cout << k << ' ';
    cout << "] (len=" << range<K>.size() << ")" << endl;
}

这会产生以下输出:

Initialising range<3>
Creating Data elements for K=2
At the moment, range<2> is [] (len=0)
At the moment, range<2> is [] (len=0)
At the moment, range<2> is [] (len=0)
Initialising range<2>
>>> Inside main()
Data elements:
Element Data(0)
Element Data(1)
Element Data(2)
Now, range<2> is [0 1 ] (len=2)

我真的不明白为什么它会这样工作。我的意思是,我希望一个const向量(或任何向量!)在使用之前被初始化,因此range<2>在我在代码中使用它的任何时候它的长度都是 2。

标签: c++

解决方案


由(非显式)模板特化产生的非局部静态存储持续时间变量的动态初始化是无序的,即不确定地排序,这意味着初始化发生的顺序是未指定的。它不考虑变量之间的依赖关系、定义顺序或实例化顺序。

因此,您的程序具有未定义的行为,因为Data<2>::elems从使用 in 实例化main,具有无序的动态初始化和使用range<2>,并且range<3>两者也具有无序的动态初始化。由于未指定是先初始化前者还是后者,因此您可能访问range<2>range<3>在它们的初始化开始之前访问,从而导致未定义的行为。

这可以通过在其初始化程序中使用std::array而不是std::vectorfor rangeand 来解决(并删除初始化程序cout中的语句),以便初始化程序成为一个常量表达式。thenrange<K>不会有动态初始化,而是常量初始化,它总是在任何动态初始化之前执行,即 beforeData<K>::elems将使用它。

此外,您应该声明rangeasconstexpr以确保初始化程序确实是一个常量表达式。否则,您可能仍然会在没有警告的情况下获得动态初始化和未定义的行为,例如,当您做出意外导致初始化程序不再是常量表达式的更改时。


推荐阅读