采用初始值设定项列表的构造函数

Constructors taking initializer lists

本文关键字:列表 构造函数      更新时间:2023-10-16

我得到了使用大括号进行统一初始化的想法。但是为什么在具有采用初始值设定项列表的构造函数的类型上使用此语法,调用该特定构造函数,即使参数仅包装在一对大括号中,即

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}};

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

如果该类有一个构造函数采用std::initializer_list,那么在列表初始化中传递大括号初始化列表时,它将是首选。

  • 否则,T的构造函数将分两个阶段考虑:

    • 所有将std::initializer_list作为唯一参数的构造函数,或者如果其余参数具有 检查默认值,并通过过载分辨率进行匹配 反对类型std::initializer_list的单个参数

    • 如果上一阶段未产生匹配项,则T的所有构造函数都参与针对以下参数集的重载解析: 由大括号初始化列表的元素组成,但有限制 只允许非缩小范围的转换。如果这个阶段 生成显式构造函数作为 复制列表初始化,编译失败(注意,简单 复制初始化,根本不考虑显式构造函数(。

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

听起来你是在寻求动力,而不是在标准中说必须这样做的地方。为此,您可以查看C++语言创建者Bjarne Stroustrup的初始列表的原始提案N1919。

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

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,也不是引入初始值设定项列表的提议版本。这又回到了 98 C++。大括号初始化器语法已经有效,但仅适用于 C 样式结构,即没有用户定义的构造函数。这是 C 的延续,它总是允许以这种方式初始化结构(和数组(,并且它将始终做同样的事情:逐个元素初始化。

该提案的重点在于允许以与那些 C 样式结构和数组相同的方式初始化适当的C++对象(如std::vector<int>(:C++ 旨在允许用户定义的类看起来像内置类型(因此例如运算符重载(,而这里有一个地方它没有。为了扭转你的问题,奇怪的不是std::vector<int>{3}调用初始化器列表构造函数,奇怪的是std::vector<std::string>{3}调用非初始化器列表构造函数。为什么它曾经调用非初始化器列表构造函数?这并不是大括号初始化的最初用途。答案是允许使用手写构造函数的固定长度容器,如下所示:

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 个元素长的东西。