The copy constructor is a special kind of constructor which creates a new object which is a copy of an existing one, and does it efficiently.
The copy constructor receives an object of its own class as an argument, and allows to create a new object which is copy of another without building it from scratch.
Here below is a simple declaration of a copy constructor
class string
{
string();
~string();
string(const string &s)
{
copy(s.m_str);
}
};
Now you can use it as follow:
string s1("hello");
string s2(s1);
string s3(string("abc"));
string s4 = s1;
you have to use const in the argument at the copy constructor to create an object as a copy of a temporary object: e.g.
string(const string &s)
.
to make things clear, you can create a new object as copy of a different object without using a copy constructor, like this:
string s4;
s4.set(s1);
this is an example of inefficient code. Since
s4
first call its constructor to build a new object and then it make a bit-wise copy of
s1
. The whole process of calling the constructor to build an object which next is being rewritten, is wasteful, takes time and resources. Copy constructor allow you to prevents this inefficiency.
Default copy constructor
If the programmer did not declared the copy constructor for a class, the compiler will add its own default copy constructor for the objects derived from that class.
Default copy constructor does a very simple operation, they will do a bit-wise (member-wise) copy of an object, which means that the object will be copied bit by bit.
string s1("hello");
string s2(s1);
string s2 = s1;
There is a danger in copying an object bit by bit, if the object contains pointers since the pointer address will be copied in the process resulting in two different objects that share the same memory buffer. You can imagine what will happen when two copies of an object calls their destructors one after the other. The first object that call its destructor will have no problems, since it will try to deallocate the pointer and succeed, but the second objects destructor try to deallocate a pointer which does not exist anymore and the whole application crashes!
For a situation where an object contain pointers we have to write our own copy constructor that will prevent situations like those mentioned above.
Copy constructor & passing objects as arguments to methods
There are 3 situations in which the copy constructor is called:
- When we make copy of an object.
- When we pass an object as an argument by value to a method.
- When we return an object from a method by value.
we saw the first scenario above, and we will now look at the other two scenarios.
When objects are passed to a function as arguments by value, a bit wise copy of the object will be passed to the function and placed on the stack, therefor the constructor of the object will not be called. It make sense if you think of it, you want to pass an object in a certain state containing the data you need for the function to process, if you wanted it in the initialization state with its default data, you could just create it inside the function instead of passing it as an argument.
When the function end, the constructor of the object will be called. This is also make sense since the object was passed by value and its data will not be needed outside the function scope.
No pay attention to this, the situation in which only the object destructor is called can make great deal of troubles. Think what will happen when the object holds a pointer to some address in the memory. When this object is passed as argument to a function the pointer in the new temporary created object will hold the same address as the original object, since its a bit wise copy. When the function ends, the destructor will free the address pointed by the pointer. From this point, if the destructor of the original object will be called it will try to free an address which already free, and we all know what it the consequences of that ...
lets look at an example to make things clear.
Here we define string class which holds a
char*
pointer:
class string
{
string(char* aStr)
{
str = new char[sizeof(aStr)];
strcpy (str,aStr);
}
~string()
{
del str;
}
char *getChars(){ return str; }
char* str;
};
now we will write a function that receive a string object as an argument.
void function (string str)
{
}
Lets look what will happen when we call this function :
void main ()
{
string str("hello");
function(str);
function(str);
}
The first time we call
function(str)
everything works properly, when the function ends, the input argument on the stack is destroyed, and its destructor will be called, and delete the pointer.
The second time we call the function , everything still works properly, but when the function ends, the constructor will try now to free the address pointed by str, and crash.
The copy constructor come to help us solve this kind of problems. Here is a solution:
class string
{
string(char* aStr)
{
str = new char[sizeof(aStr)];
strcpy (str,aStr);
}
string(string &strObj)
{
tmpStr = strObj.getChars();
str = new tmpStr[sizeof(tmpStr)];
strcpy (str,tmpStr);
}
~string()
{
del str;
}
char* str;
};
The same way we can handle the third scenario where a method returns an object:
class string
{
public:
string(char* aStr)
{
str = new char[ strlen(aStr) + 1 ];
strcpy (str,aStr);
}
string(string &strObj)
{
str = new char[ strObj.str ];
strcpy( str, strObj.str );
}
string &string::operator=(const string &s)
{
string temp( s );
std::swap( temp.str, str );
return *this;
}
~string()
{
delete[] str;
}
private:
char* str;
};
[Ash - corrected a few problems in the last version of the class, they were sticking out like a sore thumb]