The Copy Constructor

In addition to providing a default constructor and destructor, the compiler provides a default copy constructor. The copy constructor is called every time a copy of an object is made.

When you pass an object by value, either into a function or as a function's return value, a temporary copy of that object is made. If the object is a user-defined object, the class's copy constructor is called.

All copy constructors take one parameter: a reference to an object of the same class. It is a good idea to make it a constant reference, because the constructor will not have to alter the object passed in. For example:

CAT(const CAT & theCat);

Here the CAT constructor takes a constant reference to an existing CAT object. The goal of the copy constructor is to make a copy of theCat.

The default copy constructor simply copies each member variable from the object passed as a parameter to the member variables of the new object. This is called a shallow (or member-wise) copy, and although this is fine for most member variables, it does not work as intended for member variables that are pointers to objects on the heap.

A shallow or member-wise copy copies the exact values of one object's member variables into another object. Pointers in both objects end up pointing to the same memory. A deep copy, on the other hand, copies the values allocated on the heap to newly allocated memory.


If the CAT class includes a member variable, itsAge, that points to an integer on the heap, the default copy constructor will copy the passed-in CAT's itsAge member variable to the new CAT's itsAge member variable. The two objects will then point to the same memory, as illustrated in Figure 13.1.

Figure 13.1. Using the default copy constructor.


This will lead to a disaster when either CAT goes out of scope. When the object goes out of scope, the destructor is called, and it will attempt to clean up the allocated memory.

In this case, if the original CAT goes out of scope, its destructor will free the allocated memory. The copy will still be pointing to that memory, however, and if it tries to access that memory it will crash your program. Figure 13.2 illustrates this problem.

Figure 13.2. Creating a stray pointer.


The solution to this problem caused by a shallow copy is to define your own copy constructor and to allocate memory as required in the copy. Creating a deep copy allows you to copy the existing values into new memory. Listing 13.3 illustrates how to do this.

Listing 13.3. Copy Constructors
 0:  // Listing 13.3
 1:  // Copy constructors
 2:  #include <iostream>
 3:
 4:  class CAT
 5:  {
 6:  public:
 7:      CAT();                         // default constructor
 8:      CAT (const CAT &);     // copy constructor
 9:      ~CAT();                         // destructor
10:      int GetAge() const { return *itsAge; }
11:      int GetWeight() const { return *itsWeight; }
12:      void SetAge(int age) { *itsAge = age; }
13:
14:  private:
15:      int *itsAge;
16:      int *itsWeight;
17:  };
18:
19:  CAT::CAT()
20:  {
21:      itsAge = new int;
22:      itsWeight = new int;
23:      *itsAge = 5;
24:      *itsWeight = 9;
25:  }
26:
27:  CAT::CAT(const CAT & rhs)
28:  {
29:      itsAge = new int;
30:      itsWeight = new int;
31:      *itsAge = rhs.GetAge();
32:      *itsWeight = rhs.GetWeight();
33:  }
34:
35:  CAT::~CAT()
36:  {
37:      delete itsAge;
38:      itsAge = 0;
39:      delete itsWeight;
40:      itsWeight = 0;
41:  }
42:
43:  int main()
44:  {
45:      CAT frisky;
46:      std::cout << "frisky's age: " << frisky.GetAge() << "
";
47:      std::cout << "Setting frisky to 6...
";
48:      frisky.SetAge(6);
49:      std::cout << "Creating boots from frisky
";
50:      CAT boots(frisky);
51:      std::cout << "frisky's age: " << frisky.GetAge() << "
";
52:      std::cout << "boots' age: " << boots.GetAge() << "
";
53:      std::cout << "setting frisky to 7...
";
54:      frisky.SetAge(7);
55:      std::cout << "frisky's age: " << frisky.GetAge() << "
";
56:      std::cout << "boot's age: " << boots.GetAge() << "
";
57:      return 0;
58:  }


frisky's age: 5
Setting frisky to 6...
Creating boots from frisky
frisky's age: 6
boots' age:  6
setting frisky to 7...
frisky's age: 7
boots' age: 6
					

On lines 4–17, the CAT class is declared. Note that on line 7 a default constructor is declared and on line 8 a copy constructor is declared.


On lines 15 and 16, two member variables are declared, each as a pointer to an integer. Typically, there'd be little reason for a class to store int member variables as pointers, but this was done to illustrate how to manage member variables on the heap.

The default constructor, on lines 19–25, allocates room on the heap for two int variables and then assigns values to them.

The copy constructor begins on line 27. Note that the parameter is rhs. It is common to refer to the parameter to a copy constructor as rhs, which stands for right-hand side. When you look at the assignments in lines 31 and 32, you'll see that the object passed in as a parameter is on the right-hand side of the equal sign. Here's how it works:

  • On lines 29 and 30, memory is allocated on the heap. Then, on lines 31 and 32, the value at the new memory location is assigned the values from the existing CAT.

  • The parameter rhs is a CAT that is passed into the copy constructor as a constant reference. The member function rhs.GetAge() returns the value stored in the memory pointed to by rhs's member variable itsAge. As a CAT object, rhs has all the member variables of any other CAT.

  • When the copy constructor is called to create a new CAT, an existing CAT is passed in as a parameter.

Figure 13.3 diagrams what is happening here. The values pointed to by the existing CAT are copied to the memory allocated for the new CAT.

Figure 13.3. An illustration of a deep copy.


On line 45, a CAT is created, called frisky. frisky's age is printed, and then his age is set to 6 on line 48. On line 50, a new CAT is created, boots, using the copy constructor and passing in frisky. Had frisky been passed as a parameter to a function, this same call to the copy constructor would have been made by the compiler.

On lines 51 and 52, the ages of both CATs are printed. Sure enough, boots has frisky's age, 6, not the default age of 5. On line 54, frisky's age is set to 7, and then the ages are printed again. This time frisky's age is 7 but boots' age is still 6, demonstrating that they are stored in separate areas of memory.

When the CATs fall out of scope, their destructors are automatically invoked. The implementation of the CAT destructor is shown on lines 37–43. delete is called on both pointers, itsAge and itsWeight, returning the allocated memory to the heap. Also, for safety, the pointers are reassigned to NULL.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.248.37