When should we use copy constructors?

I know that the C ++ compiler creates a copy constructor for the class. In this case, we need to write a custom copy constructor? Can you give some examples?

+72
c ++ copy-constructor
Jul 19 '10 at 5:21
source share
7 answers

The copy constructor generated by the compiler performs phased copying. Sometimes this is not enough. 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, the member copying of the stored member will not duplicate the buffer (only the copy will be copied), so the first one that will be destroyed copies the shared buffer, will call delete[] successfully, and the second will be launched into undefined behavior. You need a deep copy instance (and an assignment operator).

 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; } 
+65
Jul 19 '10 at 5:22
source share

I'm a little annoyed that the Rule of Five rule is not quoted.

This rule is very simple:

Rule of Five:
Whenever you write 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 recommendation that you should follow, which follows from the need to write exclusive code:

Each resource must be managed by a dedicated object.

Here, @sharptooth code is still (mostly) fine, however, if it needs to add a second attribute to its class, it won't. 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 the new Bar throws out? How to delete the object mFoo points mFoo ? There are solutions (try / catch level function ...), they just don't scale.

The right way to handle the situation is to use the correct 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, in fact, using make_unique ), now I have exception safety !!! Isn't that exciting? And best of all, I no longer need to worry about the right destructor! I need to write my own Copy Constructor and Assignment Operator , though, since unique_ptr does not define these operations ... but it doesn't matter here;)

And so the sharptooth revisited class:

 class Class { public: Class(char const* str): mData(str) {} private: std::string mData; }; 

I do not know about you, but it is easier for me to find;)

+40
Jul 19. '10 at 7:25
source share

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
+27
Jun 04 '16 at 8:23
source share

If you have a class with dynamically allocated content. For example, you save the title of the book as char * and set the title with a new one, the copy will not work.

You need to write a copy constructor that does title = new char[length+1] and then strcpy(title, titleIn) . The copy constructor would simply make a shallow copy.

+6
Jul 19 '10 at 5:24
source share

The constructor copy is called when the object is either passed by value, or returned by value, or explicitly copied. If there is no copy constructor, C ++ creates a default copy constructor, which creates a shallow copy. If the object does not have pointers to dynamically allocated memory, it will make a shallow copy.

+2
Jul 19 '10 at 5:29
source share

It is often useful to disable copying ctor and operator = if the class does not need it. This can prevent inefficiencies such as passing an argument by value when the link is intended. Also, methods generated by the compiler may not be valid.

0
Jul 19 '10 at 7:49
source share

Let's look at the code snippet below:

 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(); produces unwanted output because there is a custom copy constructor created without code written to explicitly copy data. Thus, the compiler does not create the same.

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

Hooray ... Happy coding !!!

0
Dec 18 '18 at 0:23
source share



All Articles