复制构造函数是始终隐式定义还是仅在使用时隐式定义?

时间:2018-06-21 17:26:06

标签: c++ c++11 language-lawyer copy-constructor assignment-operator

考虑以下代码:

#include <memory>
#include <vector>

class A
{
private:
  std::vector<std::unique_ptr<int>> _vals;
};

int main()
{
  A a;
  //A a2(a);
  return 0;
}

编译器A可以毫无问题地进行编译,除非我取消注释A a2(a);行,此时它抱怨std::unique_ptr的复制构造函数被删除,因此无法复制构造{{1} }。但是,即使我将该行注释掉,编译器B也会提出投诉。也就是说,当我实际尝试使用编译器A时,它只会生成一个隐式定义的副本构造函数,而编译器B会无条件地使用它。哪一个是正确的?请注意,如果我要使用A而不是std::unique_ptr<int> _vals;,则两个编译器都正确地隐式删除了复制构造函数和赋值运算符(std::vector<std::unique_ptr<int>> _vals;有一个显式删除的复制构造函数,而std::unique_ptr没有)。

(注意:让代码在编译器B中进行编译非常容易-只需显式删除复制构造函数和赋值运算符,它就可以正常工作。这不是问题的重点;它是要了解正确的行为)

3 个答案:

答案 0 :(得分:9)

来自[class.copy.ctor]/12

  

默认使用且未定义为已删除的复制/移动构造函数是隐式定义的 ,当它用于常量评估([basic.def.odr])时( [expr.const]),或者在首次声明后明确将其默认设置。

A的副本构造函数是默认的,因此仅在使用odr时才隐式定义它。 A a2(a);就是这样的奇特用法-因此,该语句将触发其定义,从而使程序格式错误。在odr使用副本构造函数之前,不应对其进行定义。

编译器B拒绝该程序是错误的。

答案 1 :(得分:3)

注意:我的回答取决于您的评论:

  

[...]仅在Windows上,并且仅当我将A类显式列出为DLL导出时(例如,通过__declspec(dllexport)A类),才会发生这种情况。 [...]

MSDN上,我们可以了解到声明一个类dllexport会使所有成员都导出,并且需要为所有成员定义。我怀疑编译器会为所有非delete d函数生成定义,以便遵守此规则。

您可以阅读herestd::is_copy_constructible<std::vector<std::unique_ptr<int>>>::value实际上是true,我希望假设的机制(在您的案例中定义副本构造函数以用于导出)检查该值。特质(或使用类似的机制),而不是实际检查它是否可以编译。这就可以解释为什么使用unique_ptr<T>而不是vector<unique_ptr<T>>时,行为是正确的。

因此,问题在于,std::vector实际上定义了拷贝构造函数,即使它无法编译。

Imho,is_copy_constructible检查就足够了,因为在您dllexport发生的那一刻,您不知道隐式函数是否会在您所使用的地方被 odr-使用使用dllimport(甚至可能是另一个项目)。因此,我不会将其视为编译器B中的错误。

答案 2 :(得分:0)

虽然我无法确认此行为(我无法访问Windows编译器,并且OP声称该错误发生在Windows平台上的icc中),但从表面上看,问题的答案是-编译器B有一个严重错误。

特别是,当...

时,隐式声明的副本构造函数被定义为已删除。
  

T具有无法复制的非静态数据成员(已删除,   无法访问或模棱两可的副本构造函数);

https://en.cppreference.com/w/cpp/language/copy_constructor

因此,合格的编译器必须从语义上生成已删除的copy-constructor,并成功编译程序,因为此类构造函数从未调用过。