This post is part of a series of tutorials on C++

A particularly useful feature of C++ classes is the ability to overload various operators. This allows you to use instances of C++ classes in symbolic ways similar to how we create expressions by operating on primitive types.

For starters, we’ll focus on just one operator, the assignment operator (often incorrectly called “equals”, since it looks like =, but it is much nicer to get the terminology straight on this because assignment assigns a value as opposed to deciding whether or not something is equal).

Assignment operators go hand-in-hand with copy constructors, so you may want to review my tutorial of them](https://jalevine.bitbucket.io/notes/c++/2018/02/14/copy-constructors.html) if you haven’t already.

Assignment in C++ looks like this:

int lhs = 5;
int rhs = 6;
lhs = rhs;    //assign the value of rhs to lhs.  Now lhs will be 6

The above variables are named in a particularly special convention. lhs refers to “left hand side”, indicating it is on the left side of the = operator, and rhs refers to the “right hand side”, similarly indicating that it is on the right of the = operator. The effect of the line lhs = rhs is to copy the value of rhs and store it in the memory associated with lhs.

Of course, for C++ classes one might ask: “can we assign between instances of classes?”. The answer is, yes!

class Foo {
  public:
    int x;
};

int main() {
  Foo a;
  a.x = 5;
  Foo b;
  b.x = 6;
  a = b;     //assigns the "value" of b to a, in this case a.x will be 6
}

Perhaps surprisingly, the above works as one would hope. In particular, each variable in b will be copied in a. It turns out that, like copy constructors, all C++ classes as given a default assignment operator. It also turns out that, like copy constructors, the default assignment operator is not particularly clever. In particular, it shallow copies, which works just fine for members that are simple types, and works horribly for members that are pointers.

Thus, it often makes sense to write our own assignment operator and manage how copies work. In general, for any class that you’re going to create multiple instances of, you should code both a copy constructor and an assignment operator. It’s good form to have both around, since sometimes it’s not entirely clear which will be called. For example, we saw in the copy constructor tutorial) that when a data type is initialized on declaration using the equals sign, it is equivalent to calling the copy constructor and not the assignment operator:

class Foo {
  public:
    int x;
};

int main() {
  Foo a;
  a.x = 5;
  Foo b = a;  //calls the copy constructor, not the assignment operator
}

Assignment operators have a bit of a curious syntax:

class Foo {
  ...
  Foo& operator=(const Foo& rhs);
  ...
};

In the above description, you’ll see they return a reference to an instance of the class. This is so that one can do assignment chaining (e.g. a = b = c, which evaluates from right to left as a = (b = c)). Next, you’ll notice that a const reference is passed as input. Assignment operators only read from the item on the right hand side, so const enforces this behavior without the need to make a copy (indeed, you’ll be doing the copy within the function itself, so why copy at the time the function is called too).

You might also be wondering how this class member function actually executes. In C++, operatorX is handily translated to just X, for example

class Foo {
  ...
  Foo& operator=(const Foo& rhs);
  ...
};

int main() {
  Foo a, b;
  a = b;
  a.operator=(b);   //equivalent to a=b
}

You can in fact user either way to call as the above shows. In a very very very few number of cases, it will make sense to directly call the operator using .operatorX(..). Otherwise, take advantage of the syntactic sugar that C++ provides for you and just use a X b.

Another important tidbit is what does operator=() return? In general, you return *this;. You might recall that this is a point that allows member functions to access the current instance of the class you’re working with. *this dereferences this pointer, accessing the instance directly, which you then return a reference to.

Most every assignment operator you will code will thus use the following template:

Foo& Foo::operator=(const Foo& rhs) {
  //copy from rhs to the current data
  return *this;
}

Here’s an example of assignment operators in action for our ThreeLetterWord class:

Note that the line animal = cat; calls the assignment operator. Try commenting out the assignment operator declaration and definition and calling this code again. What does the second animal.print(); display with the assignment operator implemented and with the assignment operator commented out? When no assignment operator is used, only a shallow copy is made and thus changing the first letter of cat also changes the first letter of animal.

For more advanced concepts, check out Assignment Operators, Part 2.



Back to C++ Tutorials