复制构造函数是始终隐式定义,还是仅在使用时定义?
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
因此,符合规范的编译器必须在语义上生成已删除的复制构造函数,并成功编译程序,因为从未调用过此类构造函数。
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
会使所有成员导出并需要对所有成员进行定义。我怀疑编译器生成所有非 delete
d 函数的定义是为了遵守此规则。
如您所见,here,std::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
.
中的错误
考虑以下代码:
#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
因此,符合规范的编译器必须在语义上生成已删除的复制构造函数,并成功编译程序,因为从未调用过此类构造函数。
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
会使所有成员导出并需要对所有成员进行定义。我怀疑编译器生成所有非 delete
d 函数的定义是为了遵守此规则。
如您所见,here,std::is_copy_constructible<std::vector<std::unique_ptr<int>>>::value
实际上是 true
,我希望假定的机制(在您的案例中定义用于导出目的的复制构造函数)检查值这个特性(或使用类似的机制)而不是实际检查它是否会编译。这可以解释为什么当您使用 unique_ptr<T>
而不是 vector<unique_ptr<T>>
.
因此,问题是 std::vector
实际上定义了复制构造函数,即使它无法编译。
恕我直言,is_copy_constructible
检查就足够了,因为在你的 dllexport
发生的时候你不知道隐式函数是否会被 odr-used在你使用 dllimport
的地方(甚至可能是另一个项目)。因此,我不会将其视为编译器 B
.