The Assignment Operator

The assignment operator can be overloaded like any other operator. In cases where dynamically allocated memory is used, the assignment operator MUST be overloaded to avoid potential problems.

What is it?

Why haven't we heard of this before?

The compiler provides a default assignment operator, which up until now has been sufficient. The default assignment operator simply copies each of the data members from the existing object on the right hand side into the existing object on the left hand side. This is not sufficient if one or more of the data members points to dynamically allocated memory.

If this sounds strangely like the copy constructor, it is. But there are important differences.

Let's look at some code using our SmartArray class.

SmartArray a1( 50 ); // 50 ints SmartArray a2( 150 ); // 150 ints a2 = a1; // typical assignment statement Before the assignment, we had this situation in memory

The statement a2 = a1; calls the assignment operator ( operator= ) for the SmartArray class. In particular the call is a2.operator=( a1 );.

If we don't provide operator=, the compiler uses the default behavior. Like the default copy constructor, the default assignment operator does a member-by-member (shallow) assignment. We get this picture in memory which is once again fraught with problems like the copy constructor.

In fact, this is worse than the copy constructor. What happened to the memory that was in a2 before the assignment? We've caused a memory leak.

The picture we'd like to have, without causing a memory leak, is this

Here's our first attempt at writing the SmartArray assignment operator

void SmartArray::operator= (const SmartArray& rhs) { // free the memory for the left-hand object delete [ ] m_data; // now make a deep copy of the right-hand object m_size = rhs.m_size; m_data = new int [ m_size ]; for (int j = 0; j < m_size; j++ ) m_data[ j ] = rhs.m_data [ j ]; } But we're not done....
Recall that it's desirable for our classes to emulate the built-in data types. In particular, we can do the following with built-in data types... int bob, mary, sally; bob = mary = sally; // statement 1 bob = bob; // statement 2 (bob = mary) = sally; // statement 3 our objects should also support these statements, even if they may make no sense.

So, our correct version of operator= is SmartArray& // return a reference to non-const SmartArray SmartArray::operator= (const SmartArray& rhs) { if (this != &rhs) // do nothing if the code is bob = bob; { // free the memory for the current array delete [ ] m_data; // make a copy of the rhs m_size = rhs.m_size; m_data = new int [m _size ]; for (int j = 0; j < m_size; j++ ) m_data[ j ] = rhs.m_data [ j ]; } return *this; // for cascading assignment }

What's "this"

The compiler provides each object with a special variable named this.

this is a pointer to the object which invoked the member function. So whenever we write m_size, we could also have written this->m_size;, but there's no reason to do so and it's just more code.

For the assignment operator, however, this becomes important. We check for self-assignment with the condition this != &rhs which checks if the address of the object that invoked the assignment operator (the left-hand operand) is different from the address of the right-hand operand.
When operator= returns, it has to return a reference to an object? You might ask, "Which object"? The answer is "the object on the left-hand-side of the assignment" which is the one that invoked the function. How do we reference that object? Using "this". If "this" is a pointer to that object, then "*this" dereferences the pointer and is the object itself.

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