Wrox Press C++ Tutorial


The Copy Constructor in a Derived Class

You will remember that the copy constructor is called automatically when you declare an object which is initialized with an object of the same class. Look at these statements:

Box myBox(2.0, 3.0, 4.0);    // Calls constructor
Box CopyBox(myBox);          // Calls copy constructor

The first will call the constructor, accepting three double arguments, and the second will call the copy constructor. If you don't supply your own copy constructor, the compiler will supply one that copies the initializing object, member by member, to the corresponding members of the new object. So that we can see what is going on during execution, let's add our own version of a copy constructor to the class Box. We can then use this class as a base for defining the class CandyBox.

// Listing 8_05-01
class Box              // Base class definition
{
   public:
      Box(double lv=1.0, double bv=1.0, double hv=1.0)
      {
         cout << endl << "Box constructor called";
         length = lv;
         breadth = bv;
         height = hv;
      }

      // Copy constructor
      Box(const Box& initB)
      {
         cout << endl << "Box copy constructor called";
         length = initB.length;
         breadth = initB.breadth;
         height = initB.height;
      }

      // Box destructor - just to track calls
      ~Box()
      { cout << "Box destructor called" << endl; }

   protected:
      double length;
      double breadth;
      double height;
};

You'll also recall that the copy constructor needs to have its parameter specified as a reference in order to avoid an infinite number of calls to itself, caused by the need to copy an argument that is transferred by value. When the copy constructor in our example is invoked, it will output a message to the screen, so we'll be able to see from the output when this is happening.

We will use the CandyBox class from Ex8_04.cpp, shown again here:

// Listing 8_05-02
class CandyBox: public Box
{
   public:
      char* contents;

      // Derived class function to calculate volume
      double Volume()
      { return length*breadth*height; }

      // Constructor to set dimensions and contents
      // with explicit call of Box constructor
      CandyBox(double lv, double bv, double hv, 
                              char* str= "Candy"):Box(lv,bv,hv)
      {
         cout << endl <<"CandyBox constructor2 called";
         contents = new char[ strlen(str) + 1 ];
         strcpy(contents, str);
      }

      // Constructor to set contents
      // calls default Box constructor automatically
      CandyBox(char* str= "Candy")            // Constructor
      {
         cout << endl << "CandyBox constructor1 called";
         contents = new char[ strlen(str) + 1 ];
         strcpy(contents, str);
      }

      ~CandyBox()                             // Destructor
      {
         cout << "CandyBox destructor called" << endl;
         delete[] contents;
      }
};

This doesn't have a copy constructor added yet, so we'll rely on the compiler-generated version.

Try It Out - The Copy Constructor in Derived Classes

We can exercise the copy constructor that we have just defined with the following example:

// Ex8_05.cpp
// Using a derived class copy constructor
#include <iostream>          // For stream I/O
#include <string>            // For strlen() and strcpy()
using namespace std;

// Insert Box class definition here (Listing 8_05-01)

// Insert CandyBox class definition here (Listing 8_05-02)

int main()
{
   // Declare and initialize
   CandyBox ChocBox(2.0, 3.0, 4.0, "Chockies"); 
   // Use copy constructor
   CandyBox ChocolateBox(ChocBox);

   cout << endl
        << "Volume of ChocBox is " << ChocBox.Volume()
        << endl
        << "Volume of ChocolateBox is " << ChocolateBox.Volume()
        << endl;

   return 0;
}

How It Works (or why it doesn't)

When you run this example, in addition to the expected output, you'll see the following message:

Press Abort to clear the dialog and you'll see the output in the console window that you might expect. The output shows that the compiler-generated copy constructor for the derived class automatically called the copy constructor for the base class.

However, as you've probably realized, all is not as it should be. In this particular case, the compiler-generated copy constructor causes problems because the memory pointed to by the contents member of the derived class in the second object declared will point to the same memory as the one in the first object. When one object is destroyed (when it goes out of scope at the end of main()), it releases the memory occupied by the text. When the second object is destroyed, the destructor attempts to release some memory that has already been freed by the destructor call for the previous object.

The way to fix this is to supply a copy constructor for the derived class that will allocate some additional memory for the new object.

Try It Out - Fixing the Copy Constructor Problem

We can do this by adding the following code for the copy constructor to the public section of the derived class:

// Derived class copy constructor
CandyBox(const CandyBox& initCB)
{
   cout << endl << "CandyBox copy constructor called";
   // Get new memory
   contents = new char[ strlen(initCB.contents) + 1 ];
   // Copy string
   strcpy(contents, initCB.contents);
}

We can now run this new version of the last example with the same function main() to see how our copy constructor works.

How It Works

Now when we run the example, it behaves rather better and produces the output shown in the screen below:

However, there is still something wrong. The third line of output shows that the default constructor for the Box part of the object ChocolateBox is called, rather than the copy constructor. As a consequence, the object has the default dimensions rather than the dimensions of the initializing object, so the volume is incorrect. The reason for this is that when you write a constructor for an object of a derived class, you are responsible for ensuring that the members of the derived class object are properly initialized. This includes the inherited members.

The fix for this is to call the copy constructor for the base part of the class in the initialization list for the copy constructor for the CandyBox class. The copy constructor would then become:

// Derived class copy constructor
CandyBox(const CandyBox& initCB): Box(initCB)
{
   cout << endl << "CandyBox copy constructor called";
   // Get new memory
   contents = new char[ strlen(initCB.contents) + 1 ];
   // Copy string
   strcpy(contents, initCB.contents);
}

Now, the Box class copy constructor is called with the initCB object. Only the base part of the object will be passed to it, so everything will work out. If you modify the last example by adding the base copy constructor call, the output will be as shown:

The output shows that all the constructors and destructors are called in the correct sequence, and that the copy constructor for the Box part of ChocolateBox is called before the CandyBox copy constructor. The volume of the object ChocolateBox of the derived class is now the same as that of its initializing object, which is as it should be.

We have, therefore, another golden rule to remember:

If you write any kind of constructor for a derived class, you are responsible for the initialization of all members of the derived class object, including all its inherited members.


© 1998 Wrox Press