为什么C ++ 11中的类初始化程序不能使用括号?

例如,我不能写这个:

class A { vector<int> v(12, 1); }; 

我只能写这个:

 class A { vector<int> v1{ 12, 1 }; vector<int> v2 = vector<int>(12, 1); }; 

C ++ 11语言devise的区别是什么?

一个可能的原因是允许括号会立即引导我们回到最令人头疼的parsing 。 考虑以下两种types:

 struct foo {}; struct bar { bar(foo const&) {} }; 

现在,你有一个你想要初始化的typesbar的数据成员,所以你把它定义为

 struct A { bar B(foo()); }; 

但是上面所做的是声明一个名为B的函数,它通过值返回一个bar对象,并且接受一个具有签名foo() (返回一个foo并且不带任何参数)的函数的单个参数。

根据在StackOverflow上提出的问题的数量和频率来判断这个问题,这是大多数C ++程序员发现的令人惊讶和不直观的事情。 添加新的括号或等同初始值设置语法是避免这种模糊的一个机会,并从一个干净的标准开始,这可能是C ++委员会select这样做的原因。

 bar B{foo{}}; bar B = foo(); 

正如预期的那样,上面的两行都声明了一个名为B的对象。


除了上面的猜测之外,我想指出的是,在上面的例子中,你正在做两件完全不同的事情。

 vector<int> v1{ 12, 1 }; vector<int> v2 = vector<int>(12, 1); 

第一行将v1初始化为包含两个元素121的向量。 第二个创build一个包含12元素的向量v2 ,每个元素初始化为1

注意这个规则 – 如果一个types定义了一个带有initializer_list<T>构造函数,那么当该types的初始值设定项是一个braced-init-list时,该构造函数总是被视为第一个。 其他的构造函数只有在使用initializer_list才会被考虑。

非静态数据成员初始化程序的相关build议中明确提到了这种select的基本原理:

科纳提出的关于标识符范围的问题:

在2007年9月召开的科纳会议的核心工作组讨论中,一个关于初始化者标识符范围的问题出现了。 我们是否希望让类范围具有向前查找的可能性? 或者我们是否要求初始化器在被parsing的地方定义好?

需要什么:

类范围查找的动机是,我们希望能够将任何东西放入非静态数据成员的初始化程序中,以便我们可以将其放入mem初始化程序中,而不会显着地改变语义(模式直接初始化与复制初始化) :

 int x(); struct S { int i; S() : i(x()) {} // currently well-formed, uses S::x() // ... static int x(); }; struct T { int i = x(); // should use T::x(), ::x() would be a surprise // ... static int x(); }; 

问题1:

不幸的是,这使得在声明被parsing的时候,“(expression式列表)”的初始化forms不明确:

  struct S { int i(x); // data member with initializer // ... static int x; }; struct T { int i(x); // member function declaration // ... typedef int x; }; 

一个可能的解决scheme是依靠现有的规则,如果一个声明可以是一个对象或一个函数,那么这是一个函数:

  struct S { int i(j); // ill-formed...parsed as a member function, // type j looked up but not found // ... static int j; }; 

一个类似的解决scheme是应用另一个现有的规则,目前只在模板中使用,如果T可能是一个types或其他东西,那么这是另一回事; 如果我们确实是一个types的话,我们可以使用“typename”

 struct S { int i(x); // unabmiguously a data member int j(typename y); // unabmiguously a member function }; 

这两个解决scheme引入了许多可能被许多用户误解的微妙之处(comp.lang.c ++上的许多问题都certificate了为什么在块范围内的“int i();”没有声明一个默认初始化的int) 。

本文提出的解决scheme是只允许“=初始化子句”和“{初始化列表}”forms的初始化程序 。 这在大多数情况下解决了含糊问题,例如:

 HashingFunction hash_algorithm{"MD5"}; 

在这里,我们不能使用= form,因为HasningFunction的构造函数是显式的。 在特别棘手的情况下,types可能不得不提及两次。 考虑:

  vector<int> x = 3; // error: the constructor taking an int is explicit vector<int> x(3); // three elements default-initialized vector<int> x{3}; // one element with the value 3 

在这种情况下,我们必须通过使用适当的表示法来select这两种方法:

 vector<int> x = vector<int>(3); // rather than vector<int> x(3); vector<int> x{3}; // one element with the value 3 

问题2:

另一个问题是,因为我们build议没有改变初始化静态数据成员的规则,所以添加静态关键字可能会使得格式良好的初始化器不合格:

  struct S { const int i = f(); // well-formed with forward lookup static const int j = f(); // always ill-formed for statics // ... constexpr static int f() { return 0; } }; 

问题3:

第三个问题是,类范围查找可能会将编译时错误转化为运行时错误:

 struct S { int i = j; // ill-formed without forward lookup, undefined behavior with int j = 3; }; 

(除非被编译器捕获,否则我可能用j的未定义值初始化。)

build议:

CWG在科纳进行了6至3次的稻草民意调查,赞成阶级范围查询; 这就是本文提出的,非静态数据成员的初始化器限于“= initializer-clause”和“{initializer-list}”forms。

我们相信:

问题1:这个问题不会发生,因为我们不提出()符号。 =和{}初始化符号不会遇到这个问题。

问题2:添加static关键字会产生一些差异,这是最less的。

问题3:这不是一个新问题,但是与构造函数初始化函数已经存在的初始化顺序问题是一样的。