A copy constructor is a special constructor in the C ++ programming language and in some other programming languages, for example, Java , which is used to create a new object as a copy of an existing one. Such a constructor takes at least one argument: a reference to the object to be copied.
Typically, the compiler automatically creates a copy constructor for each class (known as implicit copy constructors, that is, copy constructors specified implicitly), but in some cases, the programmer creates a copy constructor, called in this case an explicit copy constructor (or βcopy constructor specified explicitlyβ). way β). In such cases, the compiler does not create implicit constructors.
The copy constructor is mainly needed when the object has a pointer or an inseparable link , such as a file , in which case you usually also need a destructor and an assignment operator (see Rule of Three ).
Content
Definition
Copying of objects is carried out by using the copy constructor and assignment operator . The copy constructor as the first parameter (with an optional modifier of type const or volatile) accepts a reference to the classβs own type. In addition to this parameter, it can have additional parameters, provided that such additional parameters have default values [1] . The following example demonstrates the correct copy constructors for class X:
X ( const X & );
X ( X & );
X ( const volatile X & );
X ( volatile X & );
X ( const X & , int = 10 );
X ( const X & , double = 1.0 , int = 40 );
The first entry of the copy constructor is the main one; other forms should be used only when necessary. The operation of copying temporary objects can only be done using the first constructor. For example:
X a = X (); // Compiles if the constructor X (const X &) is implemented, and will throw an error
// if only X (X &) is defined.
// To create an object a, the compiler will create a temporary object of the class
// X and then use the copy constructor to create the object a.
// Copying temporary objects requires a const type.
In the example below, object a is created as immutable; accordingly, when creating object b , the first copy constructor must be present.
const X a ;
X b = a ; // correct if there is X (const X &) and not correct if there is X (X &)
// since the second one does not support the type const X &
The X& View of the copy constructor is used when you need to modify the object to be copied. This is a fairly rare situation, but it is provided in the standard library by calling std::auto_ptr . The link should implement:
X a ;
X b = a ; // correct if any of the copy constructors is defined
// from the moment the link is passed
The following copy constructors (or persistent constructors) are incorrect:
X ( X );
X ( const X );
since calling these constructors will require another copy, which will lead to an infinite recursive call (i.e. an infinite loop).
There are four cases of calling the copy constructor:
- When an object is a return value
- When an object is passed (function) by value as an argument
- When an object is constructed based on another object (the same class)
- When the compiler generates a temporary object (as in the first and second cases above; as an explicit conversion, etc.)
Operations
An object can be assigned a value using one of two methods:
- Explicit assignment in expression
- Initialization
Explicit assignment in expression
Object A ;
Object B ;
A = B ; // translated as Object :: operator = (const Object &),
// this way A.operator = (B) is called
Initialization
An object can be initialized in any of the following ways.
a. By ad
Object B = A ; // translates as Object :: Object (const Object &)
b. Using function arguments
type function ( Object a );
c. Using the return value of a function
Object a = function ();
The copy constructor is used only in the latter case (initialization) and is not used instead of assignment (that is, where the assignment operator is used).
The implicit copy constructor of a class invokes the base copy constructors and copies of their members corresponding to their type. If this is a class type, then the copy constructor is called. If it is a scalar type, then the built-in assignment operator is used. And finally, if it is an array, then each element is copied in a manner appropriate to their type. [2]
Using an explicit copy constructor, the programmer can determine what to do next after copying the object.
Examples
The following examples illustrate how copy constructors work and their need.
Implicit copy constructor
#include <iostream>
class Person
{
public :
int age ;
Person ( int age ) : age ( age ) {}
};
int main ()
{
Person timmy ( 10 );
Person sally ( 15 );
Person timmy_clone = timmy ;
std :: cout << timmy . age << "" << sally . age << "" << timmy_clone . age << std :: endl ;
timmy . age = 23 ;
std :: cout << timmy . age << "" << sally . age << "" << timmy_clone . age << std :: endl ;
}
Result
10 15 10 23 15 10
As expected, timmy was copied to the new timmy_clone object. When changing the age (age) of timmy , timmy_clone did not change age: the objects are completely independent.
The compiler generated a copy constructor for us, which can be written something like this:
Person ( Person const & copy )
: age ( copy . age ) {}
Explicit Copy Constructor
The following example shows one simple class of dynamic arrays:
#include <iostream>
class Array
{
public :
int size ;
int * data ;
Array ( int size )
: size ( size ), data ( new int [ size ]) {}
~ Array ()
{
delete [] data ;
}
};
int main ()
{
Array first ( 20 );
first . data [ 0 ] = 25 ;
{
Array copy = first ;
std :: cout << first . data [ 0 ] << "" << copy . data [ 0 ] << std :: endl ;
} // (1)
first . data [ 0 ] = 10 ; // (2)
}
Result
25 25 Segmentation fault
Here, the compiler generated the copy constructor automatically. This constructor looks something like this:
Array ( Array const & copy )
: size ( copy . size ), data ( copy . data ) {}
The problem with this constructor is that it does a simple copy of the data pointer. It only copies the address, but not the data itself. And when the program reaches line (1) , the copy destructor is called (objects on the stack are destroyed automatically when their boundaries are reached). As you can see, the Array destructor deletes the data array, so when it deletes copy data, it also deletes the data first . Line (2) now receives invalid data and writes it. This leads to the famous segmentation fault.
In the case of a native copy constructor that performs deep copying , this problem will not occur:
Array ( Array const & copy )
: size ( copy . size ), data ( new int [ copy . size ])
{
std :: copy ( copy . data , copy . data + copy . size , data ); // #include <algorithm> for std :: copy
}
A new int array is created here and the contents are copied into it. Now, the copy destructor will only delete its data and not touch the data first . Line (2) no longer causes a segmentation error.
Instead of doing deep copying, you can use several optimizing strategies. This will allow a safe way to allow access to data for several objects, thereby saving memory. The copy-on-write strategy only creates a copy of data when it is being written. The reference counter contains a counter of the number of objects referencing data and deletes it only when the counter reaches zero (for example, boost :: shared_ptr).
Copy constructors and templates
The template constructor is not a copy constructor .
template < typename T > Array :: Array ( const T & copy )
: size ( copy . size ()), data ( new int [ copy . size ()])
{
std :: copy ( copy . begin (), copy . end (), data );
}
This constructor will not be used if T is an Array type.
Array arr ( 5 );
Array arr2 ( arr );
The second line will either call the non-standard copy constructor, or, in its absence, the default copy constructor.
See also
- Assignment operator