25

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

struct Foo
{
    Foo() = default;
    explicit Foo(const Foo&) = default;
};

int main()
{
    Foo bar;
    Foo foo{bar}; // error here
}

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

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

或 (clang++)

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

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

4

1 回答 1

24

您遇到了一个案例,该案例在 C++14 完成后立即通过核心问题 1467的解决方案得到解决。

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

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

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

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

[...]

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

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

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

  • 如果T是一个类类型并且初始化器列表有一个类型为cv U的元素,其中UisT或一个派生自 的类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表明上述段落中的第一个项目符号将该例外应用于所有类类型(问题说明包含相关示例)有点过分。现在子弹的开头是:
    • IfT是一个聚合类,并且 [...]
  • 论文P0398R0中包含的CWG 1518决议表明声明构造函数(甚至是默认构造函数)的类不再是聚合。explicit

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

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

于 2015-08-02T21:48:47.580 回答