构造函数采用初始化列表

Constructors taking initializer lists

我明白了用花括号统一初始化背后的想法。但是,为什么在具有采用初始化列表的构造函数的类型上使用此语法,调用该特定构造函数,即使参数仅包含在一对大括号中,即

int main(int argc, const char ** argv)
{
    vector<int> vs0{3};

    for(int v : vs0)
    {
        cout << v << ' ';
    }

    cout << '\n';

    vector<int> vs1(3);

    for(int v : vs1)
    {
        cout << v << ' ';
    }
}

/*
    Output
    3
    0 0 0
*/

为什么 vs0 是用初始化列表构造函数构造的?不应该

vector<int> v2{{3}};

为此?这有点令人困惑,特别是如果您不知道 class 有一个采用初始化列表的构造函数。

如果 class 有一个采用 std::initializer_list 的构造函数,那么在 braced-init-list 中传递它时将是首选21=].

  • Otherwise, the constructors of T are considered, in two phases:

    • All constructors that take std::initializer_list as the only argument, or as the first argument if the remaining arguments have default values, are examined, and matched by overload resolution against a single argument of type std::initializer_list

    • If the previous stage does not produce a match, all constructors of T participate in overload resolution against the set of arguments that consists of the elements of the braced-init-list, with the restriction that only non-narrowing conversions are allowed. If this stage produces an explicit constructor as the best match for a copy-list-initialization, compilation fails (note, in simple copy-initialization, explicit constructors are not considered at all).

{3} 是花括号初始化列表,那么 vector 将被初始化为包含 1 个值为 3 的元素。该行为与传递 {1, 2}{1, 2, 3} 等一致。

这听起来像是在寻求动力,而不是标准中规定必须这样做的地方。为此,您可以查看 C++ 语言创建者 Bjarne Stroustrup 的 original proposal N1919 for intializer lists

他列出了四种初始化对象的方法:

X t1 = v; // “copy initialization” possibly copy construction
X t2(v); // direct initialization
X t3 = { v }; // initialize using initializer list
X t4 = X(v); // make an X from v and copy it to t4

请注意,他说的不是 C++11,也不是引入了初始化列表的提议版本。这回到了 C++98。大括号初始化语法已经有效,但 仅适用于 C 风格结构 即没有用户定义的构造函数。这是 C 的遗留问题,它总是允许以这种方式初始化结构(和数组),并且它总是做同样的事情:逐个元素地初始化。

提案的全部要点是允许以与那些 C 样式结构和数组相同的方式初始化适当的 C++ 对象,如 std::vector<int>:C++ 旨在允许用户定义 类 看起来像内置类型(因此例如运算符重载),这是它没有的地方。为了解决您的问题,奇怪的不是 std::vector<int>{3} 调用初始化列表构造函数,奇怪的是 std::vector<std::string>{3} 调用非初始化列表构造函数。为什么它 ever 调用非初始化列表构造函数?这并不是大括号初始化最初的目的。答案是允许带有手写构造函数的定长容器,像这样:

class Vector3D {
public:
    Vector3D(double x, double y, double z) { /*...*/ }
    // ...
};
Vector3D v = {1, 2, 3}; // Ought to call non-initialiser list constructor

这就是为什么在使用大括号初始化时首选采用 std::initializer_list 的构造函数(如果可用)。对于那些了解背景的人来说,对一切都使用大括号初始化器,这已经成为一种时尚,似乎真的很反常:Foo f{7} 看起来 f 将直接包含数字 7 而后没有其他内容构造完成,并不是说它做了一些任意的事情,比如构造 7 个元素长的东西。