4.9 Copy Constructors


Through a special constructor called a copy constructor a class can define how its instances are copied. A copy constructor uses an existing object's attributes (data, state, instance variables) to initialize the new object being created.

A copy constructor is a constructor that has a certain, fixed signature. Like other constructors, the copy constructor cannot be called explicitly, but only implicitly when the C++ language dictates that a copy of an object is required.

If a class does not define a copy constructor, a default copy constructor is used that performs a byte-by-byte copy from the existing object to the new object. In many simple cases the default copy constructor is adequate. However, as the examples below will show, the default copy constructor is often inadequate. In addition, many consider it good style to always define a copy constructor even if it has the same effect as the default copy constructor.

The first of two circumstances under which a copy constructor is implicity used is when a new object is declared and initialized using an existing object. This circumstance is illustrated in the following code:

       Frame  window1("First", Location(100, 100), Shape (200,300));
        Frame  window2("Second", Location(100, 100), Shape (200,300));
        Message originalMsg("Hello World");

        Message copiedMsg( originalMsg );  // copy constructor used
                                           // to initialize new object


In this example, the declaration of the Message object copiedMsg uses the copy constructor defined for the Message class. The initial values for the data members of the object copiedMsg are initialized based on the data members in the object originalMsg. The second circumstance where a copy constructor is implicitly used is when an object is passed as a parameter by-copy. This circumstance is illustrated in the case of the StatusLine class defined below. When either the StatusLine constructor or its SetStatus method is used, a copy of the Message parameter is made because both of these parameters are passed by-copy.


StatusLine Class Interface
  Class StatusLine {

     private:
       Message statusMessage;

     public:

       StatusLine(Message msg);
       void SetStatus(Message newMsg);
   
   };


The default copy constructor is not sufficient to allow Message class objects to be copied safely in the circumstances described above. Recall that the implementation of the Message class uses a character string (a char*) to hold a pointer that defines the text to be displayed on the screen. To avoid a memory leak, the destructor for the Message class appropriately deletes this character string when the Message object is deleted. The relevant portion of the Message class is:

       class Message {
        private:
          char*    message;
          ...
        public:
          ...
          ~Message();                            // delete message 
        };

        Message::~Message(){ delete message; }

When a copy of a Message object is made using the default copy constructor, the pointer (message), but not the string to which it points, is copied. This default copying results in the following situation:


Shared Data

Both objects point to the same place in memory. As long as both objects exist, there is no problem. However, if either of the objects is deleted, the character string will be deallocated by that object's destructor, leaving the other object pointing to a place in memory that has an undefined content.

The errors that can result from this situation are as follows. In the case above, the first call on the Display method will create a copy of the originalMsg object. This copy will be destructed at the end of the Display method. Thus, both the originalMsg object and the copiedMsg object are pointing to memory with undefined content. When the second call on Display is made one of three things can happen:

  1. The correct string ("Hello World") is displayed in window2; this will occur if the deleted memory is not immediately recycled or, even if recycled, is not immediately changed. The memory may remain the same for some indeterminate period.
  2. An incorrect (garbage) string will be displayed in window2; this will occur if the deleted memory is recycled and changed to something that looks like,but may not be, a character string.
  3. The program will crash. This will occur if the deleted memory has been recycled and changed so that the system cannot interpret it as a character string.

The copy constructor for the Message class illustrates how a copy constructor is defined:

       class Message {
          private:
           ...
          public:

                Message(const Message& other);    // copy constructor

        };

The copy constructor has a single argument - a reference to an object of the same class (i.e., the Message class in this case) that is not changed (i.e., it is declared const). The input argument (other) is declared const because the copy constructor does not need to change the argument to initialize the new object.

The code for the Message class's copy constructor is:

       Message::Message(const Message& other) {
                message = copystring(other.message); }

This constructor copies the character string that is defined by the other object. In this case, when each object is destructed only its own copy of the character string will be deleted. No memory leaks are created and no dangling pointers occur.

Tasks

  1. Add a copy constructor to the Shape class. Test your code with a simple main program.
  2. Add a copy constructor to the Location class. Test your code with a simple main program.
  3. Add a copy constructor to the PolyShape class. Test your code with a simple main program.
  4. Examine the StopWatch class implementation. Is the default copy constructor sufficient to allow copies of StopWatch objects to be made safely or is a copy constructor needed?

 




©1998 Prentice-Hall, Inc.
A Simon & Schuster Company
Upper Saddle River, New Jersey 07458

Legal Statement