为什么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
初始化为包含两个元素12
和1
的向量。 第二个创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:这不是一个新问题,但是与构造函数初始化函数已经存在的初始化顺序问题是一样的。