fp_unit:
I will fix the images later. It is late now. However, you could read the images from the operator= page. The idea is the same!
Page 14:
The Copy Constructor
The very minimal expectations from the String class we could have, is that we are able to write code like this:
Code:
int main()
{
String A("Ho ho ho. Santa here!");
String B = ("Now, who's a good boy?");
return 0;
} Obviously we need to implement a suitable constructor. Let's do that right now:
Code:
#include <iostream>
#include <cstring>
using namespace std;
class String
{
public:
String(const char* = "");
~String();
private:
char* m_pChars;
};
//------------------------------------------------------- //Acts like a conversion operator: 'c-style char' to 'String'
String::String(const char *s)
{
// Need to copy the given string. if s is zero, make a null string. if (s)
{
int len = strlen(s) + 1;
m_pChars = new char[len];
strcpy(m_pChars, s);
}
else
{
m_pChars = new char[1];
*m_pChars = '\0';
}
}
//---------------------------------------------------------
String::~String() { delete[] m_pChars; }
//---------------------------------------------------------- int main()
{
String A("Ho ho ho. Santa here!");
//Or, which is the same remember? The so called 'one liners'.
String B = ("Now, who's a good boy?");
return 0;
} I've implemented the destructor as well. Notice the square brackets
[] after the
delete operator! We are deleting an array.
String::String(const char *s) is a constructor.
This one takes one argument. Therefore this constructor is automatically a (implicit) conversion operator from a "C-style string" to our String class. That is relevant because in code like
A="Hello", "Hello" isn't a String object but a c-style string.
Also notice that our class uses dynamic allocated memory.
But what if we use the class as it is now, and implement this:
Code:
int main()
{
String A("Ho ho ho. Santa here!");
String B = A;
//Or, which is the same remember? The so called 'one liners'. //String B(A); return 0;
} RED ALERT. DEFCON 5.
This code is valid, make no mistake. The default present copy constructor will make this happen. Nevertheless something sneaky happens here: after the objects
B and
A go out of scope, the class will double delete the same data. It will call the destructor for object
B, then it will call the destructor for object
A. Right now that is a problem Let me show you the situation where
A and
B are initialised:

Now let me show you the situation where object B goes out of scope and therefore, calls its destructor:
So as soon the destructor for object A is called, we have a situation. The ISO C++ standard defines that the behaviour will be
undefined. So your program might behave normally, or it might crash. I've tested it on two of my systems. One crashed indeed. But this one (the system I am working with right now) behaves as if no problem ever occurred. Usually, in (very )large programs an error will finally occur, but finding the cause of the problem will be one heck of a task.
Luckily we can solve this problem: Make a copy of the memory block. Copy it bit by bit, so both object
A and Object
B are responsible for their very own memory block. Below is this situation visualised:

Deleting Object B (or A) first causes no problems for the next deletion of the remaining object:

So to make this picture happen, we need to make a true copy of the members of the class. Well, there is only one member (
char* m_pChars ) so we won't have much work.
Here are the steps:
1) Override the default copy constructor: Code:
String(const String&)
Or in general:
T (const T&) 2) Copy members: Code:
String::String(const String& s)
{
m_pChars = new char[strlen(s.m_pChars) + 1];
strcpy(m_pChars, s.m_pChars);
} So here the final code code listing with our own copy constructor:
Code:
#include <iostream>
#include <cstring>
using namespace std;
class String
{
public:
String(const char* = "");
~String();
String(const String& );
private:
char* m_pChars;
};
//------------------------------------------------------- //Acts like a conversion operator: 'c-style char' to 'String'
String::String(const char *s)
{
// Need to copy the given string. if s is zero, make a null string. if (s)
{
int len = strlen(s) + 1;
m_pChars = new char[len];
strcpy(m_pChars, s);
}
else
{
m_pChars = new char[1];
*m_pChars = '\0';
}
}
//---------------------------------------------------------
String::~String()
{
delete[] m_pChars;
}
//----------------------------------------------------------
String::String(const String& s)
{
m_pChars = new char[strlen(s.m_pChars) + 1];
strcpy(m_pChars, s.m_pChars);
}
//---------------------------------------------------------- int main()
{
String A("Ho ho ho. Santa here!");
String B = A;
//Or, which is the same remember? The so called 'one liners'. //String B(A); return 0;
} You may want to single step through the destructor and observe that the second destructor holds valid data indeed.
In the implementation of our copy constructor we alocated m_pChars with new, but we never delete it in the body of this function.
QUESTION: Why not?
Why duhuuh. We
need that dynamic allocated memory to avoid double deletion. Let the destructor take care of it.
I've asked this question because I've seen a few universities asking the same question on quizzes. Don't let them fool you. This whole tutorial is more about basic skill and confidence building than operator overloading actually! Often the answers are so simple as long as one stays level headed with C++.
We have now implemented TWO of items of the
Rule Of Three:
1) The destructor.
2) The copy constructor.
One to go. Assignment operator overloading. That is obviously the next page. But once again, learn how to debug and debug the destructor before we implemented the copy ctor, and after we've implemented it.