18

在 C++11 中,我们可以使用“brace-or-equal-initializer”(标准中的词)进行类内初始化,如下所示:

struct Foo
{
  /*explicit*/ Foo(int) {}
};

struct Bar
{
  Foo foo = { 42 };
};

但是如果我们取消注释explicit,它就不再编译了。GCC 4.7 和 4.9 是这样说的:

error: converting to ‘Foo’ from initializer list would use explicit constructor ‘Foo::Foo(int)’

我发现这很令人惊讶。这段代码不能编译真的是 C++11 标准的意图吗?

删除=修复它:Foo foo { 42 };但我个人觉得这很难向几十年来习惯于这种形式的人解释=,而且由于标准指的是“大括号或相等初始化器”,为什么好的旧方法并不明显在这种情况下不起作用。

4

3 回答 3

13

我无法解释这背后的基本原理,但我可以重复显而易见的事情。

我发现这很令人惊讶。这段代码不能编译真的是 C++11 标准的意图吗?

§13.3.1.7

在复制列表初始化中,如果选择了显式构造函数,则初始化格式错误。


删除 = 修复了它:Foo foo { 42 };但我个人觉得这很难向几十年来习惯使用 = 形式的人解释,而且由于标准指的是“大括号或相等初始化器”,为什么好的原因并不明显旧方法在这种情况下不起作用。

Foo foo { 42 }直接初始化,而等号(带大括号)使其成为复制列表初始化。另一个答案的原因是,因为复制初始化(不带括号的等号)编译失败,所以复制列表初始化也失败也就不足为奇了,但这两个失败的原因不同。

cpp参考:

直接初始化比复制初始化更宽松:复制初始化只考虑非显式构造函数和用户定义的转换函数,而直接初始化考虑所有构造函数和隐式转换序列。

以及他们在显式说明符上的页面:

指定不允许隐式转换或复制初始化的构造函数和(自 C++11 起)转换运算符。

另一方面,对于复制列表初始化:

T 对象= { arg1 , arg2 , ... }; (10)

10) 在等号的右侧(类似于复制初始化)

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

    • 如果前一个阶段没有产生匹配,则 T 的所有构造函数都参与重载决议,以对抗由括号初始化列表的元素组成的参数集,并限制只允许非缩小转换。如果此阶段生成显式构造函数作为复制列表初始化的最佳匹配,则编译失败(注意,在简单的复制初始化中,根本不考虑显式构造函数)

如果复制列表初始化允许显式构造函数会出现什么问题?,编译失败,因为选择了显式构造函数但不允许使用。

于 2014-10-01T09:13:12.410 回答
12

如果Foo(int)explicit,那么这也不会编译:

Foo foo = 42;

因此,对于“已经习惯了几十年的表格的人”来说,表格也不能编译=也就不足为奇了。{}

于 2014-10-01T09:22:05.240 回答
7

小部件 w = {x};

这称为“复制列表初始化”。与widget w{x}的含义相同;除了不能使用显式构造函数。保证只调用一个构造函数。

来自http://herbsutter.com/2013/05/09/gotw-1-solution/

有关初始化对象的各种方法的更详细讨论,请参阅本文的其余部分。

于 2014-10-01T09:13:30.597 回答