When do we have to use copy constructors?
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?
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).
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;
}
I am a bit peeved that the rule of the Rule of Five
wasn't cited.
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:
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.
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 ;)
And therefore, sharptooth
's class revisited:
class Class
{
public:
Class(char const* str): mData(str) {}
private:
std::string mData;
};
I don't know about you, but I find mine easier ;)
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
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 seand noted in his answer, it is always safe to turn off copyability for a new class and deliberately enable it later when really needed.
How to make a class non-copyable in 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
Declare the copy-constructor with =delete
at end.
Shallow vs Deep Copy
This is the best understood case and actually the only one mentioned in the other answers. shaprtooth has covered it 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
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-object associations, converting them to inter-object associations.
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 (eg default-constructed-state) and not safe 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 observable state 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 (eg 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>".
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
.
Copy-on-write (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.
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.
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).
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
).
TODOs
This answer can be improved as follows:
上一篇: 了解术语和概念的含义
下一篇: 我们何时必须使用复制构造函数?