C++ 我们什么时候必须使用复制构造函数?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3278625/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
When do we have to use copy constructors?
提问by penguru
I know that C++ compiler creates a copy constructor for a class. In which case do we have to write a user-defined copy constructor? Can you give some examples?
我知道 C++ 编译器为类创建了一个复制构造函数。在什么情况下我们必须编写用户定义的复制构造函数?你能举一些例子吗?
采纳答案by sharptooth
The copy constructor generated by the compiler does member-wise copying. Sometimes that is not sufficient. For example:
编译器生成的复制构造函数进行成员式复制。有时这还不够。例如:
class Class {
public:
Class( const char* str );
~Class();
private:
char* stored;
};
Class::Class( const char* str )
{
stored = new char[srtlen( str ) + 1 ];
strcpy( stored, str );
}
Class::~Class()
{
delete[] stored;
}
in this case member-wise copying of stored
member will not duplicate the buffer (only the pointer will be copied), so the first to be destroyed copy sharing the buffer will call delete[]
successfully and the second will run into undefined behavior. You need deep copying copy constructor (and assignment operator as well).
在这种情况下,成员的逐成员复制stored
不会复制缓冲区(只会复制指针),因此共享缓冲区的第一个被销毁的副本将delete[]
成功调用,第二个将遇到未定义的行为。您需要深度复制复制构造函数(以及赋值运算符)。
Class::Class( const Class& another )
{
stored = new char[strlen(another.stored) + 1];
strcpy( stored, another.stored );
}
void Class::operator = ( const Class& another )
{
char* temp = new char[strlen(another.stored) + 1];
strcpy( temp, another.stored);
delete[] stored;
stored = temp;
}
回答by Matthieu M.
I am a bit peeved that the rule of the Rule of Five
wasn't cited.
我对Rule of Five
没有引用规则感到有点生气。
This rule is very simple:
这个规则很简单:
The Rule of Five:
Whenever you are writing either one of Destructor, Copy Constructor, Copy Assignment Operator, Move Constructor or Move Assignment Operator you probably need to write the other four.
五法则:
每当您编写析构函数、复制构造函数、复制赋值运算符、移动构造函数或移动赋值运算符中的任何一个时,您可能需要编写其他四个。
But there is a more general guideline that you should follow, which derives from the need to write exception-safe code:
但是您应该遵循一个更通用的准则,它源于需要编写异常安全的代码:
Each resource should be managed by a dedicated object
每个资源都应该由一个专用对象管理
Here @sharptooth
's code is still (mostly) fine, however if he were to add a second attribute to his class it would not be. Consider the following class:
这里@sharptooth
的代码仍然(大部分)很好,但是如果他要向他的类添加第二个属性,那就不会了。考虑以下类:
class Erroneous
{
public:
Erroneous();
// ... others
private:
Foo* mFoo;
Bar* mBar;
};
Erroneous::Erroneous(): mFoo(new Foo()), mBar(new Bar()) {}
What happens if new Bar
throws ? How do you delete the object pointed to by mFoo
? There are solutions (function level try/catch ...), they just don't scale.
如果new Bar
throws会发生什么?你如何删除指向的对象mFoo
?有解决方案(功能级别 try/catch ...),它们只是无法扩展。
The proper way to deal with the situation is to use proper classes instead of raw pointers.
处理这种情况的正确方法是使用适当的类而不是原始指针。
class Righteous
{
public:
private:
std::unique_ptr<Foo> mFoo;
std::unique_ptr<Bar> mBar;
};
With the same constructor implementation (or actually, using make_unique
), I now have exception safety for free!!! Isn't it exciting ? And best of all, I no longer need to worry about a proper destructor! I do need to write my own Copy Constructor
and Assignment Operator
though, because unique_ptr
does not define these operations... but it doesn't matter here ;)
使用相同的构造函数实现(或者实际上,使用make_unique
),我现在免费获得异常安全!!!是不是很刺激?最重要的是,我不再需要担心合适的析构函数!我确实需要编写自己的Copy Constructor
,Assignment Operator
但是,因为unique_ptr
没有定义这些操作......但在这里无关紧要;)
And therefore, sharptooth
's class revisited:
因此,sharptooth
重新审视了 的课程:
class Class
{
public:
Class(char const* str): mData(str) {}
private:
std::string mData;
};
I don't know about you, but I find mine easier ;)
我不了解你,但我觉得我的更容易;)
回答by Leon
I can recall from my practice and think of the following cases when one has to deal with explicitly declaring/defining the copy constructor. I have grouped the cases into two categories
我可以从我的实践中回忆起以下情况,当人们必须处理显式声明/定义复制构造函数时。我将案例分为两类
- Correctness/Semantics- if you don't provide a user-defined copy-constructor, programs using that type may fail to compile, or may work incorrectly.
- Optimization- providing a good alternative to the compiler-generated copy constructor allows to make the program faster.
- 正确性/语义- 如果您不提供用户定义的复制构造函数,则使用该类型的程序可能无法编译,或者可能无法正常工作。
- 优化- 为编译器生成的复制构造函数提供一个很好的替代方案,可以使程序更快。
Correctness/Semantics
正确性/语义
I place in this section the cases where declaring/defining the copy constructor is necessary for the correct operation of the programs using that type.
我在本节中列出了声明/定义复制构造函数对于使用该类型的程序的正确操作所必需的情况。
After reading through this section, you will learn about several pitfalls of allowing the compiler to generate the copy constructor on its own. Therefore, as seandnoted in his answer, it is always safe to turn off copyability for a new class and deliberatelyenable it later when really needed.
通读本节后,您将了解允许编译器自行生成复制构造函数的几个陷阱。因此,正如seand在他的回答中指出的那样,关闭新类的可复制性并在以后真正需要时故意启用它总是安全的。
How to make a class non-copyable in C++03
如何在 C++03 中使类不可复制
Declare a private copy-constructor and don't provide an implementation for it (so that the build fails at linking stage even if the objects of that type are copied in the class' own scope or by its friends).
声明一个私有的复制构造函数并且不提供它的实现(这样即使该类型的对象被复制到类自己的范围内或被它的朋友复制,构建也会在链接阶段失败)。
How to make a class non-copyable in C++11 or newer
如何在 C++11 或更高版本中使类不可复制
Declare the copy-constructor with =delete
at end.
用=delete
结尾声明复制构造函数。
Shallow vs Deep Copy
浅拷贝与深拷贝
This is the best understood case and actually the only one mentioned in the other answers. shaprtoothhas coveredit pretty well. I only want to add that deeply copying resources that should be exclusively owned by the object can apply to any type of resources, of which dynamically allocated memory is just one kind. If needed, deeply copying an object may also require
这是最好理解的情况,实际上是其他答案中唯一提到的情况。shaprtooth已经很好地覆盖了它。我只想补充一点,应该由对象独占拥有的深度复制资源可以应用于任何类型的资源,其中动态分配的内存只是一种。如果需要,深度复制对象可能还需要
- copying temporary files on the disk
- opening a separate network connection
- creating a separate worker thread
- allocating a separate OpenGL framebuffer
- etc
- 复制磁盘上的临时文件
- 打开单独的网络连接
- 创建一个单独的工作线程
- 分配一个单独的 OpenGL 帧缓冲区
- 等等
Self-registering objects
自注册对象
Consider a class where all objects - no matter how they have been constructed - MUST be somehow registered. Some examples:
考虑一个类,其中所有对象 - 无论它们是如何构造的 - 都必须以某种方式注册。一些例子:
The simplest example: maintaining the total count of currently existing objects. Object registration is just about incrementing the static counter.
A more complex example is having a singleton registry, where references to all existing objects of that type are stored (so that notifications can be delivered to all of them).
Reference counted smart-pointers can be considered just a special case in this category: the new pointer "registers" itself with the shared resource rather than in a global registry.
最简单的例子:维护当前存在对象的总数。对象注册只是增加静态计数器。
一个更复杂的例子是有一个单例注册表,其中存储了对该类型的所有现有对象的引用(以便通知可以传递给所有对象)。
引用计数的智能指针可以被认为只是这个类别中的一个特例:新指针将自己“注册”到共享资源而不是全局注册表中。
Such a self-registration operation must be performed by ANY constructor of the type and the copy constructor is no exception.
这种自注册操作必须由该类型的任何构造函数执行,复制构造函数也不例外。
Objects with internal cross-references
具有内部交叉引用的对象
Some objects may have non-trivial internal structure with direct cross-references between their different sub-objects (in fact, just one such internal cross-reference is enough to trigger this case). The compiler-provided copy constructor will break the internal intra-objectassociations, converting them to inter-objectassociations.
某些对象可能具有非平凡的内部结构,它们的不同子对象之间具有直接交叉引用(实际上,仅一个这样的内部交叉引用就足以触发这种情况)。编译器提供的复制构造函数将破坏内部对象内关联,将它们转换为对象间关联。
An example:
一个例子:
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?
Only objects meeting certain criteria are allowed to be copied
仅允许复制满足特定条件的对象
There may be classes where objects are safe to copy while in some state (e.g. default-constructed-state) and notsafe to copy otherwise. If we want to allow copying safe-to-copy objects, then - if programming defensively - we need a run-time check in the user-defined copy constructor.
可能存在一些类,其中对象在某种状态(例如默认构造状态)下可以安全地复制,而在其他情况下则不能安全地复制。如果我们想要允许复制安全复制对象,那么 - 如果是防御性编程 - 我们需要在用户定义的复制构造函数中进行运行时检查。
Non-copyable sub-objects
不可复制的子对象
Sometimes, a class that should be copyable aggregates non-copyable sub-objects. Usually, this happens for objects with non-observable state (that case is discussed in more detail in the "Optimization" section below). The compiler merely helps to recognize that case.
有时,一个应该是可复制的类聚合了不可复制的子对象。通常,这发生在具有不可观察状态的对象上(这种情况在下面的“优化”部分有更详细的讨论)。编译器只是帮助识别这种情况。
Quasi-copyable sub-objects
准可复制的子对象
A class, that should be copyable, may aggregate a sub-object of a quasi-copyable type. A quasi-copyable type doesn't provide a copy constructor in the strict sense, but has another constructor that allows to create a conceptual copy of the object. The reason for making a type quasi-copyable is when there is no full agreement about the copy semantics of the type.
一个应该是可复制的类,可以聚合准可复制类型的子对象。准可复制类型不提供严格意义上的复制构造函数,但具有另一个构造函数,允许创建对象的概念副本。使类型准可复制的原因是当对该类型的复制语义没有完全一致时。
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.
Or, both shallow and deep copying must be supported (none of them being the default).
例如,重新审视对象自注册的情况,我们可以争辩说,可能存在这样的情况:只有当一个对象是一个完整的独立对象时,它才必须向全局对象管理器注册。如果它是另一个对象的子对象,则管理它的责任在于它的包含对象。
或者,必须同时支持浅拷贝和深拷贝(它们都不是默认的)。
Then the final decision is left to the users of that type - when copying objects, they must explicitly specify (through additional arguments) the intended method of copying.
然后最终决定留给该类型的用户 - 在复制对象时,他们必须(通过附加参数)明确指定复制的预期方法。
In case of a non-defensive approach to programming, it is also possible that both a regular copy-constructor and a quasi-copy-constructor are present. This can be justified when in the vast majority of cases a single copying method should be applied, while in rare but well understood situations alternative copying methods should be used. Then the compiler won't complain that it is unable to implicitly define the copy constructor; it will be the users' sole responsibility to remember and check whether a sub-object of that type should be copied via a quasi-copy-constructor.
在非防御性编程方法的情况下,也可能同时存在常规复制构造函数和准复制构造函数。当在绝大多数情况下应该应用单一复制方法时,这可以证明是合理的,而在极少数但很好理解的情况下,应该使用替代复制方法。然后编译器不会抱怨它无法隐式定义复制构造函数;记住并检查是否应该通过准复制构造函数复制该类型的子对象将是用户的唯一责任。
Don't copy state that is strongly associated with the object's identity
不要复制与对象身份密切相关的状态
In rare cases a subset of the object's observablestate may constitute (or be considered) an inseparable part of the object's identity and should not be transferable to other objects (though this can be somewhat controversial).
在极少数情况下,对象可观察状态的子集可能构成(或被视为)对象身份的不可分割部分,并且不应转移到其他对象(尽管这可能有些争议)。
Examples:
例子:
The UID of the object (but this one also belongs to the "self-registration" case from above, since the id must be obtained in an act of self-registration).
History of the object (e.g. the Undo/Redo stack) in the case when the new object must not inherit the history of the source object, but instead start with a single history item "Copied at <TIME> from <OTHER_OBJECT_ID>".
对象的 UID(但这个也属于上面的“自注册”情况,因为 id 必须在自注册行为中获得)。
在新对象不能继承源对象的历史,而是以单个历史项目“ Copied at <TIME> from <OTHER_OBJECT_ID>”开始的情况下,对象的历史(例如撤消/重做堆栈)。
In such cases the copy constructor must skip copying the corresponding sub-objects.
在这种情况下,复制构造函数必须跳过复制相应的子对象。
Enforcing correct signature of the copy constructor
强制执行复制构造函数的正确签名
The signature of the compiler-provided copy constructor depends on what copy constructors are available for the sub-objects. If at least one sub-object doesn't have a real copy constructor(taking the source object by constant reference) but instead has a mutating copy-constructor(taking the source object by non-constant reference) then the compiler will have no choice but to implicitly declare and then define a mutating copy-constructor.
编译器提供的复制构造函数的签名取决于哪些复制构造函数可用于子对象。如果至少一个子对象没有真正的复制构造函数(通过常量引用获取源对象)而是有一个可变的复制构造函数(通过非常量引用获取源对象),那么编译器将别无选择但是隐式声明然后定义一个变异的复制构造函数。
Now, what if the "mutating" copy-constructor of the sub-object's type doesn't actually mutate the source object (and was simply written by a programmer who doesn't know about the const
keyword)? If we can't have that code fixed by adding the missing const
, then the other option is to declare our own user-defined copy constructor with a correct signature and commit the sin of turning to a const_cast
.
现在,如果子对象类型的“变异”复制构造函数实际上并没有改变源对象(并且只是由不知道const
关键字的程序员编写的)怎么办?如果我们无法通过添加缺失的 来修复该代码const
,那么另一种选择是使用正确的签名声明我们自己的用户定义的复制构造函数,并犯下转向const_cast
.
Copy-on-write (COW)
写时复制 (COW)
A COW container that has given away direct references to its internal data MUST be deep-copied at the time of construction, otherwise it may behave as a reference counting handle.
已经放弃对其内部数据的直接引用的 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.
尽管 COW 是一种优化技术,但复制构造函数中的此逻辑对其正确实现至关重要。这就是为什么我把这个案例放在这里而不是放在我们接下来要讲的“优化”部分。
Optimization
优化
In the following cases you may want/need to define your own copy constructor out of optimization concerns:
在以下情况下,出于优化考虑,您可能想要/需要定义自己的复制构造函数:
Structure optimization during copy
复制期间的结构优化
Consider a container that supports element removal operations, but may do so by simply marking the removed element as deleted, and recycle its slot later. When a copy of such a container is made, it may make sense to compact the surviving data rather than preserve the "deleted" slots as is.
考虑一个支持元素移除操作的容器,但可以通过简单地将移除的元素标记为已删除,并稍后回收其槽来实现。当制作此类容器的副本时,压缩幸存的数据而不是按原样保留“已删除”的插槽可能是有意义的。
Skip copying non-observable state
跳过复制不可观察状态
An object may contain data that is not part of its observable state. Usually, this is cached/memoized data accumulated over the object's lifetime in order to speed-up certain slow query operations performed by the object. It is safe to skip copying that data since it will be recalculated when (and if!) the relevant operations are performed. Copying this data may be unjustified, as it may be quickly invalidated if the object's observable state (from which the cached data is derived) is modified by mutating operations (and if we are not going to modify the object, why are we creating a deep copy then?)
一个对象可能包含不属于其可观察状态的数据。通常,这是在对象的生命周期内累积的缓存/记忆数据,以加速对象执行的某些慢速查询操作。跳过复制该数据是安全的,因为它将在执行相关操作时(以及如果!)重新计算。复制这些数据可能是不合理的,因为如果对象的可观察状态(从中派生缓存数据)通过变异操作修改(如果我们不打算修改对象,为什么我们要创建一个深度然后复制?)
This optimization is justified only if the auxiliary data is large compared to the data representing the observable state.
仅当辅助数据与表示可观察状态的数据相比较大时,这种优化才是合理的。
Disable implicit copying
禁用隐式复制
C++ allows to disable implicit copying by declaring the copy constructor explicit
. Then objects of that class cannot be passed into functions and/or returned from functions by value. This trick can be used for a type that appears to be lightweight but is indeed very expensive to copy (though, making it quasi-copyable might be a better choice).
C++ 允许通过声明复制构造函数来禁用隐式复制explicit
。然后该类的对象不能通过值传递给函数和/或从函数返回。这个技巧可以用于看起来轻量级但复制成本确实非常高的类型(尽管,使其准复制可能是更好的选择)。
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.
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
).
在 C++03 中声明复制构造函数也需要定义它(当然,如果你打算使用它)。因此,仅仅出于所讨论的问题而使用这样的复制构造函数意味着您必须编写编译器会自动为您生成的相同代码。
C++11 和更新的标准允许声明特殊成员函数(默认和复制构造函数、复制赋值运算符和析构函数),并明确要求使用默认实现(只需以 结束声明
=default
)。
TODOs
待办事项
This answer can be improved as follows:
- Add more example code
- Illustrate the "Objects with internal cross-references" case
- Add some links
这个答案可以改进如下:
- 添加更多示例代码
- 说明“具有内部交叉引用的对象”案例
- 添加一些链接
回答by Peter Ajtai
If you have a class that has dynamically allocated content. For example you store the title of a book as a char * and set the title with new, copy will not work.
如果您有一个动态分配内容的类。例如,您将一本书的标题存储为字符 * 并将标题设置为新的,复制将不起作用。
You would have to write a copy constructor that does title = new char[length+1]
and then strcpy(title, titleIn)
. The copy constructor would just do a "shallow" copy.
您必须编写一个复制构造函数,title = new char[length+1]
然后执行strcpy(title, titleIn)
. 复制构造函数只会做一个“浅”复制。
回答by josh
Copy Constructor is called when an object is either passed by value, returned by value, or explicitly copied. If there is no copy constructor, c++ creates a default copy constructor which makes a shallow copy. If the object has no pointers to dynamically allocated memory then shallow copy will do.
当对象按值传递、按值返回或显式复制时,会调用 Copy Constructor。如果没有复制构造函数,c++ 会创建一个默认的复制构造函数来进行浅拷贝。如果对象没有指向动态分配内存的指针,那么浅拷贝就可以了。
回答by seand
It's often a good idea to disable copy ctor, and operator= unless the class specifically needs it. This may prevent inefficiencies such as passing an arg by value when reference is intended. Also the compiler generated methods may be invalid.
除非类特别需要,否则禁用复制 ctor 和 operator= 通常是个好主意。这可以防止效率低下,例如在需要引用时按值传递 arg。此外,编译器生成的方法可能无效。
回答by Pankaj Kumar Thapa
Let's consider below code snippet:
让我们考虑以下代码片段:
class base{
int a, *p;
public:
base(){
p = new int;
}
void SetData(int, int);
void ShowData();
base(const base& old_ref){
//No coding present.
}
};
void base :: ShowData(){
cout<<this->a<<" "<<*(this->p)<<endl;
}
void base :: SetData(int a, int b){
this->a = a;
*(this->p) = b;
}
int main(void)
{
base b1;
b1.SetData(2, 3);
b1.ShowData();
base b2 = b1; //!! Copy constructor called.
b2.ShowData();
return 0;
}
Output:
2 3 //b1.ShowData();
1996774332 1205913761 //b2.ShowData();
b2.ShowData();
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.
b2.ShowData();
给出垃圾输出,因为创建了一个用户定义的复制构造函数,而没有编写代码来显式复制数据。所以编译器不会创建相同的。
Just thought of sharing this knowledge with you all, although most of you know it already.
只是想与大家分享这些知识,尽管你们大多数人已经知道了。
Cheers... Happy coding!!!
干杯...快乐编码!!!