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.
|