Chapter 30

Overloading Assignment Operators

In This Chapter

arrow Overloading operators — in general, a bad idea

arrow Overloading the assignment operator — why that one is critical

arrow Getting by without an assignment operator

The little symbols like +, −, =, and so on are called operators. These operators are already defined for the intrinsic types like int and double. However, C++ allows you to define the existing operators for classes that you create. This is called operator overloading.

Operator overloading sounds like a great idea. The examples that are commonly named are classes such as Complex that represent complex numbers. (Don’t worry if you don’t know what a complex number is. Just know that C++ doesn’t handle them intrinsically.) Having defined the class Complex, you can then define the addition, multiplication, subtraction, and division operators (all of these operations are defined for complex numbers). Then you write cool stuff like this:

  Complex c1(1, 0), c2(0, 1);
Complex c3 = c1 + c2;

Whoa, there, not so fast. Overloading operators turns out to be much more difficult in practice than in theory. So much so that I consider operator overloading beyond the scope of this book, with two exceptions — one of which is the subject of this chapter: overloading the assignment operator. The second operator worth overloading is the subject of the next chapter. But first things first …

Overloading an Operator

C++ considers an overloaded operator as a special case of a function call. It considers the + operator to be shorthand for the function operator+(). In fact, for any operator %, the function version is known as operator%(). So to define what addition means when applied to a Complex object, for example, you need merely to define the following function:

  Complex& operator+(const Complex& c1, const Complex& c2);

You can define what existing operators mean when applied to objects of your making, but there are a lot of things you can’t do when overloading operators. Here are just a few:

  • You can’t define a new operator, only redefine what an existing operator means when applied to your user-defined class.
  • You can’t overload the intrinsic operators such as addition of two integers.
  • You can’t affect the precedence of the operators.

In addition, the assignment operator must be a member function — it cannot be a non-member function like the addition operator just defined. (For more about member functions, see Chapter 22.)

Overloading the Assignment Operator Is Critical

The C++ language does provide an assignment operator. That’s why you can write things like the following:

  Student s1("Stephen Davis", 1234);
Student s2;
s2 = s1;     // use the default assignment operator

The C++ provided assignment operator does a member-by-member copy of each data member from the object on the right into the object on the left using each data member’s assignment operator. This is completely analogous to the C++ provided copy constructor. Remember that this member-by-member copy is called a shallow copy. (Refer to Chapter 27 for more on copy constructors and shallow copies.)

The problems inherent in the C++ provided assignment operator are similar to those of the copy constructor, only worse. Consider the following example snippet:

  class Student
{
  protected:
    char*  pszName;
    int    nID;

  public:
    Student(const char* pszNewName, int nNewID)
    {
        cout << "Constructing " << pszNewName << endl;
        int nLength = strlen(pszNewName) + 1;
        pszName = new char[nLength];
        strcpy(pszName, pszNewName);
        nID = nNewID;
    }
   ~Student()
    {
        cout << "Destructing " << pszName << endl;
        delete[] pszName;
        pszName = nullptr;
    }

    // ...other members...
};
void someFn()
{
    Student s1("Stephen Davis", 1234);
    Student s2("Cayden Amrich", 5678);

    s2 = s1;   // this is legal but very bad
}

The function someFn() first creates an object s1. The Student(const char*, int) constructor for Student allocates memory from the heap to use to store the student’s name. The process is repeated for s2.

The function then assigns s1 to s2. This does two things, both of which are bad:

  • Copies the s1.pszName pointer into s2.pszName so that both objects now point to the same block of heap memory.
  • Wipes out the previous value of s2.pszName so that the block of memory used to store the student name Cayden Amrich is lost.

Here’s what the assignment operator for Student needs to do:

  • Delete the memory block pointed at by s2.pszName (that is, act like a destructor).
  • Perform a deep copy of the string from s1.pszName into a newly allocated array in s2.pszName (act like a copy constructor). (Again, see Chapter 27 for a description of deep copying.)

tip.eps In fact, you can make this general statement: An assignment operator acts like a destructor to wipe out the current values in the object, and then acts like a copy constructor that copies new values into the object.

Looking at an Example

The following StudentAssignment program contains a Student class that has a constructor and a destructor along with a copy constructor and an assignment operator — everything a self-respecting class needs!

  //
//  StudentAssignment - this program demonstrates how to
//                create an assignment operator that
//                performs the same deep copy as the copy
//                constructor
//
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <cstring>
using namespace std;

class Student
{
  protected:
    char*  pszName;
    int    nID;

    void init(const char* pszNewName, int nNewID)
    {
        int nLength = strlen(pszNewName) + 1;
        pszName = new char[nLength];
        strcpy(pszName, pszNewName);
        nID = nNewID;
    }

    void destruct()
    {
        delete[] pszName;
        pszName = nullptr;
    }

  public:
    Student(const char* pszNewName, int nNewID)
    {
        cout << "Constructing " << pszNewName << endl;
        init(pszNewName, nNewID);
    }
    Student(Student& s)
    {
        cout<<"Constructing copy of "<< s.pszName << endl;
        init(s.pszName, s.nID);
    }

    virtual ~Student()
    {
        cout << "Destructing " << pszName << endl;
        destruct();
    }

    // overload the assignment operator
    Student& operator=(const Student& source)
    {
        // don't do anything if we are assigned to
        // ourselves
        if (this != &source)
        {
            cout << "Assigning " << source.pszName
                 << " to "       << pszName << endl;

            // first destruct the existing object
            destruct();

            // now copy the source object
            init(source.pszName, source.nID);
        }

        return *this;
    }


    // access functions
    const char* getName()
    {
        return pszName;
    }
    int getID()
    {
        return nID;
    }
};

void someFn()
{
    Student s1("Adam Laskowski", 1234);
    Student s2("Vanessa Barbossa", 5678);

    s2 = s1;
}

int main(int nNumberofArgs, char* pszArgs[])
{
    someFn();

    // wait until user is ready before terminating program
    // to allow the user to see the program results
    cout << "Press Enter to continue..." << endl;
    cin.ignore(10, ' '),
    cin.get();
    return 0;
}

The data members of this Student class are the same as the versions from earlier chapters. The constructor and copy constructor are the same as well, except that the actual work is performed in an init() function invoked from both constructors. The assignment operator can reuse the same init() function as well to perform its construction function.

The code that implements the destruct sequence has also been transferred from ~Student() to a protected destruct() member function.

Following the destructor is the assignment operator operator=(). This function first tests to see if the address of the object passed is the same as the current object. This is to detect the following case:

  s1 = s1;

In this case, the assignment operator does nothing. If the source and current objects are not the same, the function first destructs the current object and then copies the contents of the source object into the current object. Finally, it returns a reference to the current object.

The someFn() function shows how this works in practice. After first declaring two Student objects s1 and s2, someFn() executes the assignment

  s2 = s1;

which is interpreted as if it had been written as

  s2.operator=(s1);

That is, the assignment operator destructs s2 and then deep-copies the contents of s1 into s2.

The destructor invoked at the end of someFn() demonstrates that the two objects, s1 and s2, don’t both refer to the same piece of heap memory. The output from the program appears as follows:

  Constructing Adam Laskowski
Constructing Vanessa Barbossa
Assigning Adam Laskowski to Vanessa Barbossa
Destructing Adam Laskowski
Destructing Adam Laskowski
Press Enter to continue …

technicalstuff.eps The reason that the assignment operator returns a reference to the current object is to allow the following:

  s3 = s1 = s2;

Writing Your Own (or Not)

I don’t expect you to learn all the ins and outs of overloading operators; however, you can’t go too wrong if you follow the pattern set out by the Student example:

  1. Check to make sure that the left-hand and right-hand objects aren’t the same — if they are the same, return without taking any action.
  2. Destruct the left-hand object (the current object which is the same object pointed at by this).
  3. Copy-construct the left-hand object, using the right-hand object as the source.
  4. Return a reference to the left-hand object (that is, return *this;).

If all this is too much, you can use the delete keyword to delete the default assignment operator, like so:

  class Student
{
  public:
    Student& operator=(const Student&) = delete;
    
    // ...whatever else...
};

This command removes the default assignment operator without replacing it with a user-defined version. Without an assignment operator, the assignment

  s1 = s2;

generates a compiler error.

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

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