When do we have to write a user-defined copy constructor?
Asked Answered
N

7

96

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?

Nolita answered 19/7, 2010 at 5:21 Comment(2)
https://mcmap.net/q/219326/-i-defined-a-non-copy-constructor-will-a-copy-constructor-still-be-implicitly-defined-duplicateOdontalgia
One of the cases to write your own copy-ctor: When you have to do deep copy. Also note that as soon as you create a ctor, there is no default ctor created for you (unless you use default keyword).Goines
L
82

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;
}
Lorianne answered 19/7, 2010 at 5:22 Comment(14)
It doesn't perform bit-wise, but member-wise copy which in particular invokes the copy-ctor for class-type members.Mina
Dont write the assingment operator like that. Its not exception safe. (if the new throws an exception the object is left in an undefined state with store pointing at a deallocated part of memory (deallocate the memory ONLY after all operations that can throw have completed succesfully)). A simple solution is to use the copy swap idium.Persephone
@Lorianne 3rd line from the bottom you have delete stored[]; and I believe it should be delete [] stored;Edgebone
I know it's just an example, but you should point out the better solution is to use std::string. The general idea is that only utility classes that manage resources need to overload the Big Three, and that all other classes should just use those utility classes, removing the need to define any of the Big Three.Finlay
@Martin York: I agree about exception safety. But what is copy swap idiom?Lorianne
@sharp: Hm, it's spread around the site, but I don't see it as it's own question. I'll write up a nice big long explanation for it, but for now check out #276673 and #1735128 and #2144287Finlay
More C++ Idioms/Copy-and-swapMina
@sharptooth: Copy swap idium explained here: #256112 . Alternatively move the 'delete []' to the last line.Persephone
@Martin: Idiom*, by the way. :)Finlay
@GMan: You could have said that 22 minutes ago when I could still edit the comment. :-) I blame IE for not underlining bad spelling like my normal browser.Persephone
you should initialize your variables in constructor-initializer-list.Sochi
@Martin: I wanted to make sure it was carved in stone. :PFinlay
I've formally asked and explained the copy-and-swap idiom here: #3280043Finlay
You should use this signature for the assignment operator Class& Class::operator = ( const Class& another ). People are referring to it: #44198482Iceni
B
50

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 ;)

Bairam answered 19/7, 2010 at 7:25 Comment(4)
For C++ 11 - the rule of five which adds to the rule of three the Move Constructer and Move Assignment Operator.Halleyhalli
@Robb: Note that actually, as demonstrated in the last example, you should generally aim for the Rule Of Zero. Only specialized (generic) technical classes should care about handling one resource, all other classes should use those smart pointers/containers and not worry about it.Bairam
@MatthieuM. Agreed :-) I mentioned Rule of Five, since this answer is before C++11 and starts with "Big Three", but it should be mentioned that now the "Big Five" are relevant. I don't want to down vote this answer since it is correct in the context asked.Halleyhalli
@Robb: Good point, I updated the answer to mention Rule of Five instead of Big Three. Hopefully most people have moved on to C++11 capable compilers by now (and I pity those who still haven't).Bairam
J
34

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 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

  • copying temporary files on the disk
  • opening a separate network connection
  • creating a separate worker thread
  • allocating a separate OpenGL framebuffer
  • etc

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 (e.g. 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 (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>".

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:

  • Add more example code
  • Illustrate the "Objects with internal cross-references" case
  • Add some links
Jurywoman answered 4/6, 2016 at 8:23 Comment(0)
E
6

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.

Edgebone answered 19/7, 2010 at 5:24 Comment(0)
S
2

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.

Scabies answered 19/7, 2010 at 5:29 Comment(0)
L
0

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.

Lamelliform answered 19/7, 2010 at 7:49 Comment(0)
C
-1

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.

Just thought of sharing this knowledge with you all, although most of you know it already.

Cheers... Happy coding!!!

Corwin answered 18/12, 2018 at 0:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.