The Copy Constructor

When do we make copies of an object?

  1. When passing them to a function by value
  2. When returning them from a function by value
  3. When creating a new object that is initialized with a copy of an existing object
  4. When assigning one existing object to another existing object (x = y)
The first three items are handled by the copy constructor. The last item is handled by overloading the assignment ( = ) operator.

The copy constructor

A copy constructor is just what the name implies. It's a constructor that creates new objects as a copy of an existing object of the same class.

Let's go back to our old friend the Money class and let's assume we've already created a Money object named cash.

The compiler will automatically make copies of objects to pass them to functions by value or to return from functions by value.

We can explicitly tell the compiler to create new objects as a copy of cash as in the two declarations below.

Money cash( 40, 50); Money allowance( cash ); Money gift = cash;

Copy Constructor Prototype

The function prototype for the copy constructor of any class is of the form:
ClassName::ClassName (const ClassName &);.

Note that it is necessary to pass the parameter by reference and not by value. Why is this so? Note that the compiler is smart enough to know the answer and will complain if the parameter is not passed by reference.

Why haven't we seen this before?

Like the default constructor and destructor, the compiler provides a default copy constructor which up until now has been sufficient. The default copy constructor simply copies each of the data members from the existing object into the new object, just like structure assignment in C. This is not sufficient if one or more of the data members of the class points to dynamically allocated memory.

To illustrate the impact of dynamically allocated memory withn a class, we're going to implement a very simple version of the vector class called SmarArray that is used to store an unlimited number of integers.

// The SmartArray class of integers //--------------------------------- class SmartArray { public: SmartArray ( int size = 100 ); // other public methods private: int m_size; // the number of ints in the array int *m_data; // a pointer to the array of ints }; // inside main( ), we can declare some SmarArray objects SmartArray a1( 50 ); // 50 ints SmartArray a2; // 100 ints by default SmartArray a3( 200 ); // 200 ints Let's answer some questions....
  1. Where does the memory for the SmartArray get allocated? SmartArray::SmartArray (int size) : m_size( size ) { m_data = new int [ m_size ]; for (int j = 0; j < m_size; j++) m_data[ j ] = 0; } Given the declaration SmartArray a1( 50 ); we get this picture of memory

  2. Where does the memory for the SmarArrary get deallocated? SmartArray::~SmartArray( void ) { delete [ ] m_data; m_data = 0; }
  3. What else is affected?
    This time the answer is not so obvious
Consider the problems associated with dynamic memory allocation that we need to avoid. All of these situations may arise when we wish to make a copy of a SmartArray object Recalling the SmartArray objects above, let's declare a couple more objects SmartArray a1( 50 ); // 50 ints SmartArray a2; // 100 ints by default SmartArray a3( 200 ); // 200 ints SmartArray a4( a1 ); // a4 is a copy of a1 SmartArray a5 = a3; // a5 is a copy of a3 Consider what happens if we rely on the default copy constructor. The default copy constructor makes a "shallow" copy and gives us this configuration in memory.

This is clearly not good enough.

This is the picture of memory we want in order to avoid problems

To achieve this picture, we have to write our own copy constructor.

SmartArray::SmartArray (const SmartArray& array) { m_size = array.m_size; m_data = new int [ m_size ]; for (int j = 0; j < m_size; j++ ) m_data[j] = array.m_data [j]; }

Last Modified: Monday, 28-Aug-2006 10:16:05 EDT