【问题标题】:Are copy constructors defined implicitly always, or only when they are used?复制构造函数是总是隐式定义的,还是仅在使用时才定义?
【发布时间】:2018-06-21 17:26:06
【问题描述】:

考虑以下代码:

#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&lt;int&gt; _vals; 而不是std::vector&lt;std::unique_ptr&lt;int&gt;&gt; _vals;,则两个编译器都会正确地隐式删除复制构造函数和赋值运算符(std::unique_ptr 有一个显式删除的复制构造函数,而std::vector 没有)。

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

【问题讨论】:

  • 编译器B是哪个编译器?随意使用您正在使用的编译器的真实名称。
  • 我无法使用任何英特尔编译器进行复制here...您使用的是非常旧的版本吗?
  • 编译器不应该给你一个评论错误。如果是这样,它就有一个错误。
  • @R_Kapp 这应该是问题中的信息。您的“mvce”并不代表您实际在做什么。
  • 因为它只在使用__declspec(dllexport) 时发生,所以它应该是 MCVE 的一部分。 (特别是如果声明一个用于导出的类可能会导致其隐式定义的构造函数被实例化为 DLL 接口。)

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


【解决方案1】:

来自[class.copy.ctor]/12

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

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

编译器B错误拒绝程序。

【讨论】:

  • 如果 * 编译器 B* 拒绝代码,因为它实际上是写在问题中的,那么您的结论是正确的。由于 cmets 显示 __declspec(dllexport) 也在游戏中,我认为事情可能不那么琐碎。
【解决方案2】:

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

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

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

正如您所读到的herestd::is_copy_constructible&lt;std::vector&lt;std::unique_ptr&lt;int&gt;&gt;&gt;::value 实际上是true,我希望假定的机制(在您的情况下定义复制构造函数以用于导出目的)检查此特征的值(或使用类似的机制)而不是实际检查它是否会编译。这可以解释为什么当您使用 unique_ptr&lt;T&gt; 而不是 vector&lt;unique_ptr&lt;T&gt;&gt; 时,bahviour 是正确的。

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

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

【讨论】:

  • 虽然有时编译器会从不同项目的头文件中内联某些内容 - 这不是一个完美的用例吗?即,如果我没有在我的模块中声明复制构造函数或 odr-use 它,请不要在编译时包含 - 相反,将其内联到任何使用它的模块中。对我来说,这是一个完全有效的解决方案(尤其是隐式定义的复制构造函数应该相当简单)——这会导致任何其他问题吗?
  • @R_Kapp:其他项目如何知道您(或您的编译器)是否在您的 dll 中定义了复制构造函数,或者当且仅当存在 odr 时它是否应该生成一个“内联” -采用?如果定义在头文件中,则可以内联,但如果函数是隐式的,则在项目 Y 的编译器时无法判断 dll X 是否提供定义。由于A的复制构造函数没有被隐式删除,因为std::vector的复制构造函数也没有被删除,所以应该生成一个隐式定义。
  • 这是一个公平的观点......我想一个更简单的解决方法是始终内联(而不是导出)隐式定义的复制构造函数。这样,您就不会违反 ODR(没有任何东西被导出),并且它仍然只在实际使用 odr 时才被定义。
  • (如果您愿意,请随时告诉我,我应该将此作为一个单独的问题提出)另外,这在带有 GNU 的 Linux 上是如何工作的?该类也被列为“用于导出”,并且我没有隐式生成该函数。如果我要在另一个模块/项目中使用它,那将如何工作?我的猜测是它会像我上面建议的那样做一些事情(即,如果它是隐式的,则总是内联它),但两个操作系统之间可能存在一些我不知道的特定差异。
【解决方案3】:

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

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

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

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

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

【讨论】:

  • std::vector 是可复制的。正如我在问题中提到的,如果我将std::vector&lt;std::unique_ptr&lt;int&gt;&gt; _vals 替换为std::unique_ptr&lt;int&gt; _vals,我不明白这个问题-std::unique_ptr 不可复制,因此复制构造函数被正确地隐式删除。 std::vector,这是编译器所拥有的,但它是可复制的。
  • @R_Kapp 为什么你认为std::unique_ptrs 中的std::vector 是可复制的?
  • @R_Kapp: std::vector 不是一个类型。 std::vector&lt;std::unique_ptr&lt;int&gt;&gt; 不可复制。
  • std::vector&lt;T&gt; 定义了一个复制构造函数。它没有被删除、无法访问或模棱两可。碰巧它在这种情况下使用的模板参数(std::unique_ptr)是不可复制的,但这不会改变std::vector的定义
  • 请参阅 this link 作为 std::vector&lt;std::unique_ptr&lt;int&gt;&gt; 与此答案中提供的描述不匹配的证据。
猜你喜欢
  • 2012-09-16
  • 2011-06-23
  • 2018-08-03
  • 1970-01-01
  • 2011-03-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多