显式复制构造函数和统一初始化

Posted

技术标签:

【中文标题】显式复制构造函数和统一初始化【英文标题】:Explicit copy constructor and uniform initialization 【发布时间】:2015-10-24 21:57:19 【问题描述】:

显式复制构造函数不允许 Foo foo = bar; 之类的东西,并将复制用法强制为 Foo foo(bar);。此外,显式复制构造函数也不允许从函数中按值返回对象。但是,我尝试用大括号替换复制初始化,就像这样

struct Foo

    Foo() = default;
    explicit Foo(const Foo&) = default;
;

int main()

    Foo bar;
    Foo foobar; // error here

我得到了错误 (g++5.2)

错误:没有匹配的函数调用 'Foo::Foo(Foo&)'

或 (clang++)

错误:结构初始化程序中的元素过多

删除explicit 使代码在g++ 下可编译,clang++ 仍然失败并出现同样的错误(感谢@Steephen)。这里发生了什么?统一初始化是否被视为初始化列表构造函数(胜过所有其他构造函数)?但是如果是这样的话,为什么在拷贝构造函数不显式的情况下程序会编译呢?

【问题讨论】:

clang 即使没有explicit关键字coliru.stacked-crooked.com/a/1cca94237ee00ea3也会显示错误 @Steephen 确实,我没有看到。所以这是另一个时髦的语言律师类型问题:) 我会说编译器选择聚合初始化foo @Jarod42 你可能是对的,考虑到错误消息,这是有道理的。 似乎是这样,因为添加 int 成员提供 no viable conversion from 'Foo' to 'int' : Demo (并且在没有 explicit 的情况下,gcc 的行为仍然不同...) 【参考方案1】:

在 C++14 定稿后,您遇到了 Core issue 1467 解决方案立即解决的案例。

首先让我们注意 foo 类是一个聚合。您的代码正在为foo 执行direct-list-initialization。列表初始化的规则在 [8.5.4p3] 中。

在 C++14 中(引自 N4140,最接近已发布标准的工作草案),上面的段落开头为:

定义了 T 类型的对象或引用的列表初始化 如下:

如果 T 是聚合,则执行聚合初始化 (8.5.1)。

[...]

因此,如果您的类是聚合类,编译器会尝试进行聚合初始化,但失败了。

这被认为是一个问题,并在工作草案中得到了解决。引用当前版本 N4527,上述段落现在以:

定义了T 类型的对象或引用的列表初始化 如下:

如果T 是一个类类型并且初始化列表有一个cv U 类型的元素,其中UT 或派生自T 的类,这 对象从该元素初始化(通过复制初始化 复制列表初始化,或直接初始化 直接列表初始化)。 否则,如果T 是一个字符数组并且初始化列表有一个元素是一个适当类型的字符串字面量 (8.5.2),初始化按该部分所述执行。 否则,如果 T 是一个聚合,则执行聚合初始化 (8.5.1)。

[...]

您的示例现在属于第一个要点所描述的情况,并且 foo 使用默认的复制构造函数直接列表初始化(无论它是否为 explicit,因为它是直接初始化)。

也就是说……如果编译器实现了缺陷报告中的解决方案。

GCC 5.2.0(和 6.0.0 主干)似乎可以这样做,但似乎有一个与 explicit 相关的错误。 Clang 3.6.0 没有,但 3.8.0 主干可以,而且正确(explicit 无关紧要)。 MSVC 14 有,但 IDE 中的 IntelliSense 没有(bar 下的波浪线 - 看起来 IntelliSense 使用的 EDG 编译器也没有更新)。

更新:自编写此答案以来,工作草案已通过与问题中的示例和上述解释相关的几种方式进行了进一步修改:

CWG 2137 表示上面引用的段落中的第一个项目符号将该异常应用于所有类类型(问题说明包含相关示例)有点过分。现在子弹的开头是:
如果 T 是一个聚合类并且 [...]
论文P0398R0 中包含的CWG 1518 的分辨率表明声明explicit 构造函数(甚至是默认构造函数)的类不再是聚合。

这并没有改变这样一个事实,即在实施所有更改之后,问题中的示例无论有无 explicit; 都可以正常工作。值得知道的是,使它工作的底层机制发生了轻微的变化。

请注意,所有这些更改都是缺陷报告的解决方案,因此它们应该适用于编译器处于 C++14 和 C++11 模式时。

【讨论】:

有趣。虽然我仍然不明白的一件事是,如果只是将复制构造函数更改为 explicit Foo(const Foo&) 而不是 = default;,为什么代码在没有 compaints (MSVC+GCC) 的情况下编译? @Christophe 因为Foo 在这种情况下不再是聚合 - 它有一个用户提供的构造函数。

以上是关于显式复制构造函数和统一初始化的主要内容,如果未能解决你的问题,请参考以下文章

如果复制列表初始化允许显式构造函数会出现啥问题?

显式默认构造函数

显式移动构造函数?

C++:考虑但不调用构造函数的特殊性

需要使用互斥锁显式定义的复制构造函数

显式复制构造函数