Industrial Training




Copy Constructors and Assignment Operator

Although using the assignment operator is fairly straightforward, correctly implementing an overloaded assignment operator can be a little more tricky than you might anticipate. There are two primary reasons for this. First, there are some cases where the assignment operator isn’t called when you might expect it to be. Second, there are some issues in dealing with dynamically allocated memory (which we will cover in the next lesson).

The assignment operator is used to copy the values from one object to another already existing object. The key words here are “already existing”. Consider the following example:


Cents cMark(5); // calls Cents constructor
Cents cNancy; // calls Cents default constructor
cNancy = cMark; // calls Cents assignment operator

In this case, cNancy has already been created by the time the assignment is executed. Consequently, the Cents assignment operator is called. The assignment operator must be overloaded as a member function.

What happens if the object being copied into does not already exist? To understand what happens in that case, we need to talk about the copy constructor.

The copy constructor

Consider the following example:



The copy constructor

Consider the following example:



   Cents cMark(5); // calls Cents constructor
Cents cNancy = cMark; // calls Cents copy constructor!
   
   


Because the second statement uses an equals symbol in it, you might expect that it calls the assignment operator. However, it doesn’t! It actually calls a special type of constructor called a copy constructor. A copy constructor is a special constructor that initializes a new object from an existing object.

The purpose of the copy constructor and the assignment operator are almost equivalent — both copy one object to another. However, the assignment operator copies to existing objects, and the copy constructor copies to newly created objects.

The difference between the copy constructor and the assignment operator causes a lot of confusion for new programmers, but it’s really not all that difficult. Summarizing:



  • If a new object has to be created before the copying can occur, the copy constructor is used.
  • If a new object does not have to be created before the copying can occur, the assignment operator is used

There are three general cases where the copy constructor is called instead of the assignment operator:



  • When instantiating one object and initializing it with values from another object (as in the example above).
  • When passing an object by value.
  • When an object is returned from a function by value.


In each of these cases, a new variable needs to be created before the values can be copied — hence the use of the copy constructor.

Because the copy constructor and assignment operator essentially do the same job (they are just called in different cases), the code needed to implement them is almost identical.

An overloaded assignment operator and copy constructor example

Now that you understand the difference between the copy constructor and assignment operator, let’s see how they are implemented. For simple classes such as our Cents class, it is very straightforward.

Here is a simplified version of our Cents class:



class Cents
{
private:
    int m_nCents;
public:
    Cents(int nCents=0)
    {
        m_nCents = nCents;
    }
}


First, let’s add the copy constructor. Thinking about this logically, because it is a constructor, it needs to be named Cents. Because it needs to copy an existing object, it needs to take a Cents object as a parameter. And finally, because it is a constructor, it doesn’t have a return type. Putting all of these things together, here is our Cents class with a copy constructor.



class Cents
{
private:
    int m_nCents;
public:
    Cents(int nCents=0)
    {
        m_nCents = nCents;
    }
 
    // Copy constructor
    Cents(const Cents &cSource)
    {
        m_nCents = cSource.m_nCents;
    }
};


A copy constructor looks just like a normal constructor that takes a parameter of the class type. However, there are two things which are worth explicitly mentioning. First, because our copy constructor is a member of Cents, and our parameter is a Cents, we can directly access the internal private data of our parameter. Second, the parameter MUST be passed by reference, and not by value. Can you figure out why?

The answer lies above in the list that shows the cases where a copy constructor is called. A copy constructor is called when a parameter is passed by value. If we pass our cSource parameter by value, it would need to call the copy constructor to do so. But calling the copy constructor again would mean the parameter is passed by value again, requiring another call to the copy constructor. This would result in an infinite recursion (well, until the stack memory ran out and the the program crashed). Fortunately, modern C++ compilers will produce an error if you try to do this:

C:\Test.cpp(431) : error C2652: 'Cents' : illegal copy constructor: first parameter must not be a 'Cents' The first parameter in this case must be a reference to a Cents!

Now let’s overload the assignment operator. Following the same logic, the prototype and implementation are fairly straightforward:



class Cents
{
private:
    int m_nCents;
public:
    Cents(int nCents=0)
    {
        m_nCents = nCents;
    }
 
    // Copy constructor
    Cents(const Cents &cSource)
    {
        m_nCents = cSource.m_nCents;
    }
 
    Cents& operator= (const Cents &cSource);
 
};
 
Cents& Cents::operator= (const Cents &cSource)
{
    // do the copy
    m_nCents = cSource.m_nCents;
 
    // return the existing object
    return *this;
}


A couple of things to note here: First, the line that does the copying is exactly identical to the one in the copy constructor. This is typical. In order to reduce duplicate code, the portion of the code that does the actual copying could be moved to a private member function that the copy constructor and overloaded assignment operator both call. Second, we’re returning *this so we can chain multiple assigments together:



cMark = cNancy = cFred = cJoe; // assign cJoe to everyone

If you need a refresher on chaining, we cover that in the section on overloading the I/O operators.

Finally, note that it is possible in C++ to do a self-assignment:



cMark = cMark; // valid assignment

In these cases, the assignment operator doesn’t need to do anything (and if the class uses dynamic memory, it can be dangerous if it does).

It is a good idea to do a check for self-assignment at the top of an overloaded assignment operator. Here is an example of how to do that:



Cents& Cents::operator= (const Cents &cSource)
{
    // check for self-assignment by comparing the address of the
    // implicit object and the parameter
    if (this == &cSource)
        return *this;
 
    // do the copy
    m_nCents = cSource.m_nCents;
 
    // return the existing object
    return *this;
}


Note that there is no need to check for self-assignment in a copy-constructor. This is because the copy constructor is only called when new objects are being constructed, and there is no way to assign a newly created object to itself in a way that calls to copy constructor.

Default memberwise copying

Just like other constructors, C++ will provide a default copy constructor if you do not provide one yourself. However, unlike other operators, C++ will provide a default assignment operator if you do not provide one yourself!

Because C++ does not know much about your class, the default copy constructor and default assignment operators it provides are very simple. They use a copying method known as a memberwise copy (also known as a shallow copy). We will talk more about shallow and deep copying in the next lesson.



Hi I am Pluto.