CSCI 175: C++: The Big Three

The "big three" member functions in a class (according to Cline and Lomow, authors of the C++ FAQ) are the copy constructor, assignment operator, and destructor.

The rule of thumb in writing classes is that if you need one of these member functions, you need all three.

The copy constructor

When we instantiate an integer and initialize it in a statement like
int i = 0;

we are implicitly using a copy constructor that copies the value of 0 into the integer object we are creating.

In terms of the word classes we have been defining, this is analogous to both of the following:

#include "DynamicWord.h"
void main()
{
  DynamicWord a; // no copy constructor needed
  DynamicWord b(a); // need a copy constructor to copy a into b
  DynamicWord b = a; // this is converted into DynamicWord b(a) so
                     //  also needs a copy constructor
}

If a copy constructor is not specifically defined for a class, the C++ compiler synthesizes one that simply does a bitwise copy: in the example above, each bit of a is copied into b. This is fine for classes in which all the data is statically allocated, but not for dynamic memory allocations, since what is copied is the address to the dynamic memory, no new memory is allocated to make a true copy of the data. Thus, whenever dynamic memory is used, a copy constructor should be explicitly defined.

Fortunately in our case, the constructor is quite easy and we can copy from the Assign function we already have written:

In the DynamicWord.h file we need to add (as a public function)

DynamicWord(DynamicWord);

In the DynamicWord.C file we need to add

DynamicWord::DynamicWord(DynamicWord wordin)
{
  word = new char [wordin.maxlength];  // get same amount as wordin had
  memset(word, '\0', wordin.maxlength);
  maxlength = wordin.maxlength; 

  // Now, copy:
  strcpy(word, wordin.word);
  length = wordin.length;
}

(In the above example we can't simply call Assign from the constructor because Assign calls delete on word but we have not allocated any space.)

Practice

Add a copy constructor that copies a character string (char []) into a word as it is created, so that a declaration like
DynamicWord word1("Gary");

compiles and runs correctly.

The "const" type modifier

There is one dubious part of our above example. Notice that we pass the copy constructor a value parameter: DynamicWord wordin

How is wordin passed to the constructor? It must be copied, i.e. a copy constructor is used to pass it in.

Rather than force the compiler to completely copy an object, the const type modifier tells it to simply pass the reference to the object, but treat it as a constant so that the passed-in object cannot be modified.

The modified code is as follows:

In the DynamicWord.h file we need to add (as a public function)

DynamicWord(const DynamicWord &);

In the DynamicWord.C file we need to add

DynamicWord::DynamicWord(const DynamicWord & wordin)
{
  word = new char [wordin.maxlength];  // get same amount as wordin had
  memset(word, '\0', wordin.maxlength);
  maxlength = wordin.maxlength; 

  // Now, copy:
  strcpy(word, wordin.word);
  length = wordin.length;
}

The Assignment Operator

If the class definition does not contain an overloaded version of the assignment operator=, the C++ compiler synthesizes one that does a bitwise copy, so statements like
DynamicWord a;
DynamicWord b;
b = a;

will always compile.

Like the copy constructor, this is disastrous in the case of a class using dynamic memory since only the pointer to the data is copied. Thus, you should generally overload this operator yourself.

In the case of our DynamicWord, the Assign function does nearly everything we want. There are two differences between it and the operator= function. First, the name, which is easy to modify by operator overloading. The second difference is that operator= returns a reference to the object to which it just assigned a value. If we overload, we should also do this.

Thus, our overloaded prototype for operator= on a DynamicWord looks like:

DynamicWord & operator= (const DynamicWord &);

The definition of the function looks nearly identical to the Assign function:

DynamicWord & DynamicWord::operator= (const DynamicWord & w)
{ // place contents of w into this word
  // First, allocate the space
  delete [] word; // get rid of our old allocation
  word = new char [w.maxlength];  // get same amount as w had
  memset(word, '\0', w.maxlength);
  maxlength = w.maxlength; 

  // Now, copy:
  strcpy(word, w.word);
  length = w.length;

  // Here's the new trick:
  return *this;
}

Our new trick for the assignment operator is to return a reference to the object itself. Every class is automatically provided a variable called this which is a pointer to the object. By returning *this we are returning the dereference of the pointer, which is the object itself.

The Destructor

We have already discussed the destructor operator, which is automatically called when an object is no longer in the scope of the current function. As with the copy constructor and the assignment operator, we need it because of the dynamic memory allocation.

I won't duplicate the destructor code we already wrote, but I will note that, again according to the C++ FAQ authors, generally one notices first that they need an explicit destructor, and then notices the need for the other two functions.


Back to Day 14