关于c ++:我们什么时候必须使用复制构造函数?

When do we have to use copy constructors?

我知道C++编译器为类创建一个复制构造函数。在哪种情况下,我们必须编写一个用户定义的复制构造函数?你能举几个例子吗?


(P)The copy constructor generated by the compiler does member-wise copying.有时这还不够。For example:(p)字母名称(P)In this case member-wise copying of EDOCX1 indigenous member will not duplicate the buffer(only the point will be copied),so the first to be destroyed copy sharing the buffer will call EDOCX1 indiscully 1 and the second will run in to undefiined behavior.You need deep copying copy constructor(and assignment operator as well).(p)字母名称


(P)I am a bit peeven that the rule of the EDOCX1 commercial 4 wasn't cited.(p)(P)这条规则很简单:(p)布尔奇1(P)但是,有一个更为一般性的指导方针,你应该遵循,这一指导方针的产生源于需要起草例外――安全守则:(p)布尔奇1(P)Here EDOCX1(原文如此)Fine,however if he were to add a second attribute to his class it would not be.Consider the following class:(p)字母名称(P)如果EDOCX1音标6 Throws会发生什么?你是如何从EDOCX1找到目标的?There are solutions(function level try/catch…),they just don't scale.(p)(P)The proper way to deal with the situation is to use proper classes instead of raw pointers.(p)字母名称(P)With the same constructor implementation(or actually,using EDOCX1 simplication 8 nable),I now have exception safety for free!不!是的,我不知道。最棒的是,我不需要讨论一个驱逐舰!I do need to write my own EDOCX1 penography 9 common and EDOCX1,10 commency though,because EDOCX1But it doesn't matter here;)(p)(P)And Therefore,EDOCX1 theographic 12's class revisited:(p)字母名称(P)I don't know about you,but I find mine easier;(p)


我可以从我的实践中回忆起,当必须处理显式声明/定义复制构造函数时,我会想到以下情况。我把这些案件分为两类好的。

  • 正确性/语义-如果不提供用户定义的复制构造函数,使用该类型的程序可能无法编译,或者可能工作不正确。
  • 优化-为编译器生成的复制构造函数提供了一个很好的替代方案,使程序更快。

正确性/语义

在本节中,我将说明声明/定义复制构造函数对于使用该类型的程序的正确操作是必要的。好的。

在阅读本节之后,您将了解允许编译器自己生成复制构造函数的几个陷阱。因此,正如Seand在他的回答中指出的,关闭新类的可复制性并在以后真正需要时故意启用它总是安全的。好的。如何在C++ 03中制作一个不可复制的类

声明一个私有的复制构造函数,不要为它提供实现(这样,即使该类型的对象是在类自己的作用域或由它的朋友复制的,构建也会在链接阶段失败)。好的。如何使类在C++ 11或更新中不可复制

在末尾声明带有=delete的复制构造函数。好的。浅拷贝与深拷贝

这是最容易理解的情况,实际上是其他答案中提到的唯一一个。沙普托已经很好地解决了这个问题。我只想补充一下,应该由对象独占的深度复制资源可以应用于任何类型的资源,其中动态分配的内存只是一种类型。如果需要,深度复制对象也可能需要好的。

  • 复制磁盘上的临时文件
  • 打开单独的网络连接
  • 创建单独的工作线程
  • 分配单独的OpenGL帧缓冲区

自注册对象

考虑一个类,其中所有对象(不管它们是如何构造的)都必须以某种方式注册。一些例子:好的。

  • 最简单的示例:维护当前现有对象的总数。对象注册只是增加静态计数器。好的。

  • 一个更复杂的例子是有一个singleton注册表,其中存储对该类型的所有现有对象的引用(以便通知可以传递给所有对象)。好的。

  • 引用计数的智能指针在此类别中可以被视为一种特殊情况:新的指针"注册"到共享资源而不是全局注册表中。好的。

此类自注册操作必须由该类型的任何构造函数执行,复制构造函数也不例外。好的。具有内部交叉引用的对象

有些对象可能具有不平凡的内部结构,在其不同的子对象之间具有直接的交叉引用(事实上,仅一个这样的内部交叉引用就足以触发这种情况)。编译器提供的复制构造函数将中断内部对象内关联,并将其转换为对象间关联。好的。

一个例子:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
struct MarriedMan;
struct MarriedWoman;

struct MarriedMan {
    // ...
    MarriedWoman* wife;   // association
};

struct MarriedWoman {
    // ...
    MarriedMan* husband;  // association
};

struct MarriedCouple {
    MarriedWoman wife;    // aggregation
    MarriedMan   husband; // aggregation

    MarriedCouple() {
        wife.husband = &husband;
        husband.wife = &wife;
    }
};

MarriedCouple couple1; // couple1.wife and couple1.husband are spouses

MarriedCouple couple2(couple1);
// Are couple2.wife and couple2.husband indeed spouses?
// Why does couple2.wife say that she is married to couple1.husband?
// Why does couple2.husband say that he is married to couple1.wife?

只允许复制符合特定条件的对象

在某些状态(例如,默认构造状态)下,对象可以安全地复制,而在其他状态下复制则不安全。如果我们希望允许安全复制对象,那么——如果是防御性编程——我们需要在用户定义的复制构造函数中进行运行时检查。好的。不可复制的子对象

有时,应该是可复制的类会聚合不可复制的子对象。通常,这种情况发生在具有不可观测状态的对象上(这种情况将在下面的"优化"部分中更详细地讨论)。编译器只是帮助识别这种情况。好的。准可复制子对象

应该是可复制的类可以聚合准可复制类型的子对象。准可复制类型在严格意义上不提供复制构造函数,但具有另一个允许创建对象概念副本的构造函数。使类型具有准可复制性的原因是,对于类型的复制语义没有完全一致的时候。好的。

For example, revisiting the object self-registration case, we can argue that
there may be situations where an object must be registered with the global
object manager only if it is a complete standalone object. If it is a
sub-object of another object, then the responsibility of managing it is with
its containing object.

Ok.

Or, both shallow and deep copying must be supported (none of them being the default).

Ok.

然后,最终决定权留给该类型的用户——在复制对象时,他们必须(通过其他参数)显式地指定要复制的方法。好的。

在编程的非防御方法中,也可能同时存在常规复制构造函数和准复制构造函数。在绝大多数情况下,应采用单一复制方法,而在极少但理解良好的情况下,应采用其他复制方法,这是合理的。那么编译器就不会抱怨它不能隐式地定义复制构造函数;记住并检查该类型的子对象是否应该通过准复制构造函数进行复制是用户的唯一责任。好的。不要复制与对象标识强相关的状态

在极少数情况下,对象的可观察状态的一个子集可能构成(或被认为)对象身份的不可分割部分,不应转移到其他对象(尽管这可能有些争议)。好的。

实例:好的。

  • 对象的uid(但这个也属于上面的"自注册"案例,因为在自注册行为中必须获得ID)。好的。

  • 当新对象不能继承源对象的历史时,对象的历史记录(例如,撤消/重做堆栈),而是从"copied at

在这种情况下,复制构造函数必须跳过复制相应的子对象。好的。强制复制构造函数的正确签名

编译器提供的复制构造函数的签名取决于哪些复制构造函数可用于子对象。如果至少有一个子对象没有真正的复制构造函数(通过常量引用获取源对象),而是有一个可变的复制构造函数(通过非常量引用获取源对象),那么编译器将别无选择,只能隐式声明,然后定义一个可变的复制构造函数。好的。

现在,如果子对象类型的"变化的"复制构造函数实际上没有改变源对象(并且只是由一个不知道const关键字的程序员编写的),该怎么办?如果我们不能通过添加缺少的const来修复该代码,那么另一种选择是声明我们自己的用户定义的复制构造函数,并使用正确的签名,并犯转向const_cast的罪。好的。书面副本(COW)

在构建时,必须对直接引用其内部数据的Cow容器进行深度复制,否则它可能会充当引用计数句柄。好的。

Though COW is an optimization technique, this logic in the copy constructor
is crucial for its correct implementation. That is why I placed this case here
rather than in the"Optimization" section, where we go next.

Ok.

优化

在以下情况下,出于优化考虑,您可能需要/需要定义自己的复制构造函数:好的。复制过程中的结构优化

考虑一个支持元素删除操作的容器,但可以通过简单地将删除的元素标记为已删除,然后在以后回收其槽来实现。当复制这样一个容器时,压缩剩余的数据而不是保留"已删除"的槽是有意义的。好的。跳过复制不可见状态

对象可能包含不属于其可观测状态的数据。通常,这是在对象的生命周期中积累的缓存/内存化数据,以加速对象执行的某些较慢的查询操作。跳过复制该数据是安全的,因为它将在何时(如果!)执行相关操作。复制此数据可能是不合理的,因为如果对象的可观察状态(从中派生缓存数据)通过改变操作进行了修改(如果我们不打算修改对象,那么为什么要创建一个深度副本?),则复制可能会很快失效。好的。

只有当辅助数据比代表可观测状态的数据大时,这种优化才是合理的。好的。禁用隐式复制

C++允许通过声明复制构造函数EDCOX1(0)来禁用隐式复制。然后,该类的对象不能通过值传递到函数和/或从函数返回。这个技巧可以用于一个看起来很轻但复制起来确实非常昂贵的类型(尽管,使其具有准可复制性可能是一个更好的选择)。好的。

In C++03 declaring a copy constructor required defining it too (of course, if
you intended to use it). Hence, going for such a copy constructor merely out
of the concern being discussed meant that you had to write the same code that
the compiler would automatically generate for you.

Ok.

C++11 and newer standards allow declaring special member functions (the
default and copy constructors, the copy-assignment operator, and the
destructor) with an explicit request to use the default implementation
(just end the declaration with =default).

Ok.

托多斯

This answer can be improved as follows:

Ok.

  • Add more example code
  • Illustrate the"Objects with internal cross-references" case
  • Add some links

好啊。


(P)如果你有一个分类,你已经动态的满足。比如说,你把一本书的所有权当作一张图表,并用新的所有权来赋予它,它将不起作用。(p)(P)你会写一个copy constructor that does EDOCX1 penographic 2 and then EDOCX1 penographic 3.The copy constructor would just do a"shallow"copy.(p)


(P)Copy constructor is called when an object is either passed by value,returned by value,or explicitly copied.如果有非铜建造者,C++creates a default copy constructor which make a shallow copy.如果目标没有指向动态的记忆,那么他们就应该继续下去。(p)


(P)It's often a good idea to disable copy ctor,and operator=unless the class specifically needs it.This may prevent impunities such as passing an arg by value when reference is intended.同样,产生的方法也可能是无效的。(p)


(P)Let's consider below code snippet:(p)字母名称字母名称(P)EDOCX1 13 communal gives junk output because there is a user-defined copy-constructor created with no code written to copy data explicitly.So compiler does not create the same.(p)(P)只是想和你分享这一知识,尽管你知道这一点。(p)(P)Cheers…快乐代码!不!(p)