【问题标题】:Is a copy constructor always created by default?是否总是默认创建复制构造函数?
【发布时间】:2014-06-16 05:53:35
【问题描述】:

直到今天我才知道,在创建新类时会创建四个默认值。 “默认构造函数”、“析构函数”、“复制构造函数”和“赋值运算符”。但是今天我在浏览一篇 C++ 文章时,它说可能存在默认情况下不创建复制构造函数的情况。

  1. 这是真的吗?
  2. 如果是,在哪些情况下?
  3. 在这些情况下,该类的实例如何按值传递?

【问题讨论】:

  • 参见例如here 在“已删除隐式声明的复制构造函数”下。在这些情况下,除非您指定自己的复制构造函数,否则您根本无法按值传递对象。

标签: c++ copy-constructor


【解决方案1】:

1) 是的,可能存在默认不创建复制构造函数的情况。

2) 隐式声明的默认构造函数被删除的条件在12.8 复制和移动类对象[class.copy]中列出:

12.8.7 是关于其他特殊成员函数的声明如何影响隐式声明的复制构造函数。每当类声明复制构造函数、移动构造函数或移动赋值运算符时。如果它声明了其中任何一个,那么您不会得到一个隐式声明的。

...

7 如果类定义没有显式声明一个副本 构造函数,一个是隐式声明的。如果类定义 声明一个移动构造函数或移动赋值运算符, 隐式声明的复制构造函数被定义为已删除;除此以外, 它被定义为默认值(8.4)。后一种情况被弃用,如果 类具有用户声明的复制赋值运算符或用户声明的 析构函数。

12.8.11 是关于数据成员和基类如何影响隐式声明的复制构造函数。本质上,如果该类有任何不可复制的数据成员或基类,则隐式声明的复制构造函数为deleted:

11 隐式声明的复制/移动构造函数是内联公共 其类的成员。类 X 的默认复制/移动构造函数 被定义为删除 (8.4.3) 如果 X 有:

——一个变体成员,带有 非平凡的对应构造函数和 X 是类联合类,

— 类类型 M(或其数组)的非静态数据成员,它不能 被复制/移动,因为重载决议(13.3),适用于 M 相应的构造函数,导致歧义或函数 从默认构造函数中删除或不可访问,

- 一个直接或虚拟基类 B,由于重载而无法复制/移动 决议(13.3),适用于 B 的相应构造函数, 导致歧义或功能被删除或无法访问 来自默认构造函数,

——任何直接或虚拟基类或 具有已删除的析构函数的类型的非静态数据成员或 无法从默认构造函数访问,

——用于副本 构造函数,右值引用类型的非静态数据成员,或

...

3) 您可以声明和定义(通过提供实现或defaulting 复制构造函数或移动复制构造函数,或两者兼而有之。

【讨论】:

  • 嗯,这里肯定缺少一些东西...例如,当您拥有不可复制构造的成员时(例如,引用或其他具有已删除/不可访问的复制构造函数的类),没有默认的复制构造函数。 (是的,它被删除了,而不仅仅是“未创建”,但这可能是 OP 最感兴趣的情况)
  • @MatteoItalia 是的,在这些情况下,构造函数被隐式删除。我正在寻找标准中列出的部分。我会尽快编辑问题。
【解决方案2】:

是的,没错。例如,如果类的成员不可复制/可分配(例如,成员具有私有赋值运算符和私有复制构造函数),那么您将无法依赖包含类的默认复制构造函数。如果您希望包含类在这些情况下是可复制/可分配的,那么您需要明确定义这些操作。

话虽如此,在大多数情况下,您应该避免按值传递。在大多数情况下,您应该通过常量引用传递对象或传递对象的智能指针(例如std::unique_ptr)。与能够重用现有实现的情况相比,按值传递(以及任何进行不必要复制的代码)将产生效率较低的代码。此外,对于多态对象,按值传递会导致“切片”(功能从运行时类型被截断为对象被复制到的声明类型),因此按值传递特别危险和错误-在处理任何可能被继承的数据类型时容易发生。

编辑
为了澄清上述内容......就通过常量引用传递与通过值传递而言,决定应取决于对象的大小以及复制的成本。 Boost 在“调用特征”(call_traits<T>::param_type) 中提供了一种方便的机制,可以根据对象的大小自动在值和 const 引用之间进行选择。在做出此决定时,区分值类型(行为类似于原语的对象——例如,通过重载各种运算符并且可复制、可分配且不能被继承)和用户定义的多态类型也很有用。每当你有一个声明虚方法的类型时,作为一般经验法则,该对象应该通过引用或 const 引用传递以避免我上面提到的切片。

就通过智能指针传递而言,这通常在转移或共享所有权时完成(否则您通常应该只传递对相关对象的引用)。

【讨论】:

  • 你说then you won't be able to rely on a default copy constructor for the containing class这意味着它创建了复制构造函数还是完全没有?
  • 出于所有实际目的,如果您编写调用复制构造函数的代码并且您没有显式实现一个,您将得到编译器错误。现在,如果这是因为生成了默认实现但依赖于私有方法,或者因为它根本没有生成,那么这对我来说没有什么区别(语言律师往往对这类事情非常迂腐) .
  • 关于“在大多数情况下” - OP 发现最常见的内容在很大程度上取决于他们的问题域,所以虽然我一般不同意,但我不确定没有这种观察有多大用处几个反例。支持数学类+* 等运算符的“值语义类型”通常按值接受左侧参数,operator= 通常使用复制和交换习语编写,第一个参数复制构造等。也许像“当你需要当前值和修改版本时......”这样的指导方针。
  • 我很少发现将智能指针作为参数有多大用处,尤其是unique_ptr。另一方面,按价值传递事物,我经常这样做。这取决于参数的目的(例如,无论如何您都需要副本的参数),这就是为什么“在大多数情况下,您应该避免”的建议不是很有帮助,imo。
  • @TonyD:在这种情况下,您将返回对本地的引用。哦,你的运算符是无限递归的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-28
  • 1970-01-01
  • 1970-01-01
  • 2014-02-13
  • 2016-03-25
  • 2015-02-24
相关资源
最近更新 更多