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

Are copy constructors defined implicitly always, or only when they are used?

考虑以下代码:

#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 的复制构造函数被删除,因此我不能复制构造 A。然而,即使我将该行注释掉,编译器 B 也会提出投诉。也就是说,编译器 A 仅在我实际尝试使用它时生成一个隐式定义的复制构造函数,而编译器 B 会无条件地这样做。哪一个是正确的?请注意,如果我使用 std::unique_ptr<int> _vals; 而不是 std::vector<std::unique_ptr<int>> _vals;,两个编译器都会正确地隐式删除复制构造函数和赋值运算符(std::unique_ptr 有一个显式删除的复制构造函数,而 std::vector没有)。

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

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

特别是,隐式声明的复制构造函数被定义为已删除,当...

T has non-static data members that cannot be copied (have deleted, inaccessible, or ambiguous copy constructors);

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

因此,符合规范的编译器必须在语义上生成已删除的复制构造函数,并成功编译程序,因为从未调用过此类构造函数。

来自[class.copy.ctor]/12

A copy/move constructor that is defaulted and not defined as deleted is implicitly defined when it is odr-used ([basic.def.odr]), when it is needed for constant evaluation ([expr.const]), or when it is explicitly defaulted after its first declaration.

A 的复制构造函数是默认的,因此只有在 odr-used 时才会隐式定义。 A a2(a); 就是这样一个 odr-use - 所以正是那个语句会触发它的定义,这会使程序格式错误。在复制构造函数被 ODR 使用之前,不应定义它。

编译器B错误拒绝程序

注意:我的回答是基于您的评论:

[...] it's only on Windows, and only when I explicitly list class A as a DLL export (via, e.g., class __declspec(dllexport) A) that this happens. [...]

MSDN 上,我们可以了解到声明 class dllexport 会使所有成员导出并需要对所有成员进行定义。我怀疑编译器生成所有非 deleted 函数的定义是为了遵守此规则。

如您所见,herestd::is_copy_constructible<std::vector<std::unique_ptr<int>>>::value 实际上是 true,我希望假定的机制(在您的案例中定义用于导出目的的复制构造函数)检查值这个特性(或使用类似的机制)而不是实际检查它是否会编译。这可以解释为什么当您使用 unique_ptr<T> 而不是 vector<unique_ptr<T>>.

时 bahviour 是正确的

因此,问题是 std::vector 实际上定义了复制构造函数,即使它无法编译。

恕我直言,is_copy_constructible 检查就足够了,因为在你的 dllexport 发生的时候你不知道隐式函数是否会被 odr-used在你使用 dllimport 的地方(甚至可能是另一个项目)。因此,我不会将其视为编译器 B.

中的错误