8

考虑以下代码:

#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 中编译很容易 - 只需显式删除复制构造函数和赋值运算符,它就可以正常工作。这不是问题的重点;它是为了理解正确的行为。)

4

3 回答 3

9

来自[class.copy.ctor]/12

默认且未定义为已删除的复制/移动构造函数使用 odr ([basic.def.odr])、需要常量评估 ([expr.const]) 或在第一次声明后显式默认。

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

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

于 2018-06-21T17:53:20.247 回答
3

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

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

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

正如您可以在此处阅读的那样,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发生的那一点上,您无法知道隐式函数是否会在您使用的地方dllimport(甚至可能是另一个项目)被使用。因此,我不会将其视为 compiler 中的错误B

于 2018-06-21T20:20:21.630 回答
0

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

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

T 具有无法复制的非静态数据成员(已删除、不可访问或不明确的复制构造函数);

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

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

于 2018-06-21T17:40:55.493 回答