C++11:使用“= {}”进行内部初始化与显式构造函数不兼容。

19

在C++11中,我们可以使用“大括号或等号初始化器”(标准中的措辞)进行类内初始化,如下所示:

在C++11中,我们可以使用“大括号或等号初始化器”(标准中的措辞)进行类内初始化,如下所示:

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 };但我个人觉得这种方式更难向习惯于使用带有等号=的人解释,而且由于标准引用到了“大括号或等号初始化”,所以为什么传统的方式在这种情况下行不通并不明显。


2
{} 初始化器语法有点像是一种 hack,而且它还有很多奇怪的边界情况,比如这个。 - M.M
我以为你必须使用双花括号 - 试试这样写:Foo foo = { { 42 } }; - Carl Burnett
3
@Matt McNabb:这不是{}本身就是一个hack,而是使用=并希望出现省略构造的方式才是一个hack,但我个人更喜欢这种写法。无论如何,由于省略构造只是一种优化而非保证,忽略explicit会导致额外的意外构造。因此,当构造函数标记为explicit时,要求代码显式反映这种风险 - Foo foo = Foo{ 42 };是合理的。这种冗长的写法促使人们思考和简化,虽然有些繁琐。 - Tony Delroy
1
@TonyD同意,但你可能对省略有所误解。根据Herb Sutter的说法,实际上省略是有保证的。话虽如此,我不知道他基于什么来做出这种断言。 - Konrad Rudolph
1
@KonradRudolph 嗯。是的,8.5实际上让我走了一条不好的路:虽然它(根据我的理解)暗示了Foo x={a};是复制初始化,但实际上它是8.5.4中涵盖的复制列表初始化。而且,复制列表初始化与直接列表初始化相同,除非选择了一个“显式”的构造函数,否则会被阻止。与Foo x=a;不同,没有逻辑或者不合适的临时变量被创建。因此,这个可以工作 -- 被阻止的复制/移动, Foo a={x}; 样式,编译通过。如果没有{}将不能编译。 - Yakk - Adam Nevraumont
显示剩余9条评论
3个回答

13

我无法解释其中的逻辑,但我可以重申显而易见的事实。

我觉得这很惊讶。C++11标准真的是要这段代码不能编译吗?

§13.3.1.7

在复制列表初始化中,如果选择了一个显式的构造函数,则该初始化过程是非法的。
移除等号即可解决问题:Foo foo { 42 };,但我个人认为这种方式更难向几十年来一直使用等号的人解释清楚。由于标准中提到了“花括号或等号初始化”,所以在这种情况下,为什么老方法不起作用并不明显。

Foo foo { 42 }直接初始化,而等号(带有花括号)则使其成为复制列表初始化。另一个答案认为,因为复制初始化(不带花括号的等号)编译失败,那么它也会因为不同的原因而失败,也就不足为奇了。

cppreference:

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

以下是关于显式说明符的页面:

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

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

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

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

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

    • 如果前一个阶段没有产生匹配,则T的所有构造函数都参与重载决议,其中参数集合由大括号初始化列表的元素组成,限制只允许非收窄转换。如果此阶段将显式构造函数作为复制列表初始化的最佳匹配,则编译失败(请注意,在简单的复制初始化中,根本不考虑显式构造函数)
正如如果允许使用显式构造函数进行复制列表初始化会出现什么问题?中所讨论的那样,编译失败是因为选择了显式构造函数但不允许其使用。

12

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

Foo foo = 42;

对于已经习惯于几十年来使用=形式的人来说,使用{}形式同样无法编译,这并不令人惊讶。


5
Foo foo = 42 隐式地创建了一个 Foo prvalue 来初始化 foo。而对于 Foo foo = {42},则并没有隐式地创建一个 Foo。注:prvalue (pure rvalue) 是 C++ 中的一种值类别,表示表达式返回的纯右值。 - dyp
我和 @dyp 一样:我明白为什么 Foo foo = 42 不行,而且我也没问题。但是,我仍然不理解为什么 Foo foo = { 42 } 不行……似乎从另一个答案来看,这可能是标准所预期的,但它似乎有点……糟糕。 - John Zwinck

7

widget w = {x};

这被称为“复制列表初始化”。它的意思与 widget w{x} 相同,只是不能使用显式构造函数。保证只调用单个构造函数。

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接