考虑以下代码:
#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中进行编译非常容易-只需显式删除复制构造函数和赋值运算符,它就可以正常工作。这不是问题的重点;它是要了解正确的行为)
答案 0 :(得分:9)
默认使用且未定义为已删除的复制/移动构造函数是隐式定义的 ,当它用于常量评估([basic.def.odr])时( [expr.const]),或者在首次声明后明确将其默认设置。
A
的副本构造函数是默认的,因此仅在使用odr时才隐式定义它。 A a2(a);
就是这样的奇特用法-因此,该语句将触发其定义,从而使程序格式错误。在odr使用副本构造函数之前,不应对其进行定义。
编译器B拒绝该程序是错误的。
答案 1 :(得分:3)
注意:我的回答取决于您的评论:
[...]仅在Windows上,并且仅当我将A类显式列出为DLL导出时(例如,通过__declspec(dllexport)A类),才会发生这种情况。 [...]
在MSDN上,我们可以了解到声明一个类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
实际上定义了拷贝构造函数,即使它无法编译。
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,并成功编译程序,因为此类构造函数从未调用过。