I can recall from my own practice and think about the following cases when I need to deal with an explicit declaration / definition of a copy constructor. I grouped cases into two categories
- Correctness / semantics . If you do not provide a custom copy constructor, programs using this type may not compile or may not work correctly.
- Optimization - providing a good alternative to the copy constructor created by the compiler allows you to speed up the program.
Correctness / Semantics
In this section, I put cases where the declaration / definition of the copy constructor is necessary for the correct operation of programs using this type.
After reading this section, you will learn about several errors that allow the compiler to generate a copy constructor on its own. Therefore, since seand is noted in the answer, you can always disable the option to reinstall for a new class and intentionally enable it later when it is really needed.
How to make a class not copied in C ++ 03
Declare a private copy constructor and not provide an implementation for it (so that assembly is not performed at the build stage, even if objects of this type are copied in their own area or in friends).
How to make a class not copied in C ++ 11 or later
Declare a copy constructor with =delete at the end.
Invalid copy
This is the most understandable case and actually the only one mentioned in other answers. shaprtooth has, it's pretty good. I just want to add that deep copy resources, which should be exclusively owned by the object, can be applied to any types of resources, of which dynamically allocated memory is just one kind. If necessary, deep copying may require
- copy temporary files to disk
- opening a separate network connection
- creating a separate workflow
- highlighting a separate OpenGL framebuffer
- etc.
Self-registration facilities
Consider a class where all objects — no matter how they were built — MUST be registered somehow. Some examples:
The simplest example: saving the total number of existing objects. Registering objects is simply an increase in the static counter.
A more complex example is the singleton registry, which stores links to all existing objects of this type (so that notifications can be delivered to all of them).
Read smart pointers can only be considered as a special case in this category: a new pointer "registers" itself with a shared resource, and not in the global registry.
Such a self-registration operation must be performed by any constructor of the ANY type, and the copy constructor is no exception.
Cross-referenced Objects
Some objects may have a nontrivial internal structure with direct cross-references between their various sub-objects (in fact, one such internal cross-reference is enough to cause this case). The compiler created by the copy constructor breaks the internal associations inside the object , converting them into associations between objects .
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 matching certain criteria are allowed to be copied.
There may be classes in which objects can be copied if in some state (for example, the default state) and are not safe to copy otherwise. If we want to allow copying of objects with copy protection, then - if the programming is protected - we need to check the runtime in the custom copy constructor.
Not copied sub-objects
Sometimes the class to be copied aggregates non-copied sub-objects. This usually happens for objects with an unobservable state (this case is discussed in more detail in the Optimization section below). The compiler just helps to recognize this case.
Quasi-Copyable Sub Objects
The class to be copied can aggregate a sub-object of a quasicopy type. A quasi-copied type does not provide a copy constructor in the strict sense, but has a different constructor that allows you to create a conceptual copy of the object. The reason for creating a quasicopying type is the lack of full agreement on the semantics of the type copy.
For example, reviewing the case of self-registration of an object, we can argue that there may be situations when an object should be registered in the global Object Manager only if it is a complete stand-alone object. If it is a sub-object of another object, then the responsibility for managing it is associated with its containing object.
Or, both shallow and deep copying should be supported (none of them are standard).
Then the final decision is provided to users of this type - when copying objects, they must explicitly indicate (through additional arguments) the intended method of copying.
In the case of a non-protective approach to programming, it is also possible to have both a regular copy constructor and a quasicopy constructor. This can be justified when in the vast majority of cases one copying method should be used, and in rare but well understood situations alternative copying methods should be used. Then the compiler will not complain that it cannot implicitly define a copy constructor; it will be solely the responsibility of users to remember and check whether a sub-object of this type should be copied using the quasicopy constructor.
Do not copy state that is strongly associated with the object identifier
In rare cases, a subset of the observed object may constitute (or be considered) an integral part of the identifier of the object and should not be transferred to other objects (although this may be somewhat contradictory).
Examples:
The UID of the object (but this also applies to the case of “self-registration” above, since the identifier must be obtained as a result of self-registration).
The history of the object (for example, the Undo / Redo stack) when the new object should not inherit the history of the original object, but instead start with one element of the history "Copied to <TIME> from <OTHER_OBJECT_ID>".
In such cases, the copy constructor should skip copying the corresponding sub-objects.
Ensuring the proper copy constructor signature
The signature of the copy constructor provided by the compiler depends on which copy constructors are available for sub-objects. If at least one subobject does not have a real copy constructor (taking the source object by a permalink), but instead has a mutating constructor instance (using the source object by a mutable link), then the compiler will have no choice but to implicitly declare, and then define the mutating constructor instance.
Now, what if a “mutating” copy instance of a sub-object type does not actually mutate the original object (and was just written by a programmer who does not know about the const key)? If we cannot fix this code by adding the missing const , then another option is to declare our own custom copy constructor with the correct signature and commit the sin of accessing const_cast .
Copy to Write (COW)
The COW container that provided direct links to its internal data MUST be deeply copied during construction, otherwise it may behave like a reference counter.
Although COW is an optimization method, this logic in the copy constructor is crucial for its proper implementation. That's why I posted this case here and not in the "Optimization" section, where we go further.
Optimization
In the following cases, you may need / need to define your own copy constructor for optimization reasons:
Structure optimization during copying
Consider a container that supports element removal operations, but can do this by simply marking the deleted element as deleted and recycling its slot later. When a copy of such a container is created, it may be advisable to compress the remaining data, rather than saving the “deleted” slots as they are.
Skip copying an unobserved state
An object may contain data that is not part of its observed state. Typically, this is cached / memoized data accumulated over the lifetime of an object, in order to speed up some slow request operations performed by the object. It is safe to skip copying this data as it will be recounted when (and if!) The corresponding operations are performed. Copying this data may be unreasonable, since it can be quickly invalidated if the state of the observed object (from which the cached data is generated) is changed by mutating operations (and if we do not change the object, why do we create deep copy?)
This optimization is justified only if the auxiliary data are large compared with the data representing the observed state.
Disable Implicit Copying
C ++ allows you to disable implicit copying by declaring the copy constructor explicit . Then objects of this class cannot be transferred to functions and / or returned from functions by value. This trick can be used for a type that looks easy, but really very expensive to copy (although making it quasi-copyable might be a better choice).
In C ++ 03, declaring a copy constructor also requires its definition (of course, if you intended to use it). Consequently, switching to such a copy constructor simply wasn’t a topic of concern, which meant that you had to write the same code that the compiler would automatically generate for you.
C ++ 11 and newer standards allow you to declare special member functions (default constructors and copies, copy assignment operator and destructor) with an explicit request to use the default implementation (just end the declaration with =default ).
Todos
This answer can be improved as follows:
- Add sample code
- Illustrate the case of "Objects with internal cross-references"
- Add multiple links