Home Articles Books Downloads FAQs Tips

What's Wrong With This Code? Volume #5


Copy constructors
by Chris Uzdavinis

For reference, here is a copy of the code.

#include <iostream>
using std::cout;
using std::endl;

class Base
{
public:
  Base()                                    { cout << "1" << endl;}
  Base(Base const & rhs)                    { cout << "2" << endl;}
  virtual ~Base()                           { cout << "3" << endl;}
};


class Derived : public Base
{
public:
  Derived() : x_(0)                         { cout << "4" << endl;}
  Derived(int x) : x_(x)                    { cout << "5" << endl;}
  Derived(Derived const & rhs) : x_(rhs.x_) { cout << "6" << endl;}
  ~Derived() { }

  int get_x() const { return x_; }
private:
  int x_;
};


main()
{
  Derived d1(999);
  // copy construct d2 from d1
  Derived d2(d1);
}

The output of the program is :

1
5
1
6
3
3

The first two lines of output correspond to the construction of d1. Everything is fine here. The next two lines correspond to the copy construction of d2. The last two lines come from the Base destructor.

What is surprising is that Base's copy constructor is never called. We would see a 2 in the output somewhere if it was. When d2 is created, it calls Base's *default* constructor, then Derived's copy constructor. Why isn't the Base copy constructor called?

Since the programmer supplied the copy constructor for Derived, the compiler gives full responsibility to the programmer to implement it completely. This responsibility means that the derived copy constructor is obligated to EXPLICITLY call the base class copy constructor. If the derived class does not fulfill this obligation, then the compiler will default construct the base part of the newly created object. It is, afterall, what is supposed to get called by default.

So why is this a big deal? When we create a copy of d1, we want a complete copy of it. d2 should be an identical clone of d1. This includes both the base part of d1, and the derived part. Our code has a bug in its cloning routine. The derived part of d1 is copied correctly, but the base part is not. d2 ends up being a partial clone of d1.

This is a major bug that is very common. Copy construction that doesn't explicitly call the base class copy constructor usually has unexpected results, and may contribute to more bugs than most people realize. In most cases, one would expect that the copy constructor actually copy the values from the right-hand parameter. But when the copy constructor of the derived class is coded incorrectly, only the derived parts of the right hand parameter get copied. When this happens, it can be very difficult to detect.

If you have written any derived objects that have copy constructors, it is worth a minute of your time to look at each of them to ensure that the base copy constructor is explicitly called. Here is a corrected version of Derived. Only the copy constructror has changed.

class Derived : public Base
{
public:
  Derived() : x_(0)                         { cout << "4" << endl;}
  Derived(int x) : x_(x)                    { cout << "5" << endl;}

  // explicitly call base's copy ctor
  Derived(Derived const & rhs)
    : Base(rhs)
    , x_(rhs.x_)                            { cout << "6" << endl;}

  ~Derived() { }

  int get_x() const { return x_; }
private:
  int x_;
};

After fixing the copy constructor, the output becomes

1
5
2
6
3
3

Which is what we most likely expect. Lines 3 and 4 correspond to the copy construction of d2. Notice that the default constructor for the base class is gone (#1). It has been replaced with the copy constructor for the base class (#2). Remember, the order of initialization for a derived object is first to call the constructor of its base class(s), in order of declaration from left to right. Then call the member constructors (initializer list), then execute the body of this constructor.) Destructors are called in the opposite order.



Copyright © 1997-2002 by Harold Howe.
All rights reserved.