Operator overloading

Introduction

For int, a + b and cout << a just work. For a class of your own, C++ lets you say what they mean by writing functions named operator+, operator==, operator<< and so on. This is operator overloading: a + b becomes a call of operator+.

Operator overloading lets your own types read like built-in ones: vectors added with +, money compared with <, and any object printed with cout <<. Used with care, it makes code shorter and clearer.

How it works

Arithmetic, comparison and printing

#include <iostream>
using namespace std;

class Vec {
    int x, y;
public:
    Vec(int x, int y) : x(x), y(y) {}
    Vec operator+(const Vec& o) const {
        return Vec(x + o.x, y + o.y);
    }
    Vec operator*(int k) const {
        return Vec(x * k, y * k);
    }
    bool operator==(const Vec& o) const {
        return x == o.x && y == o.y;
    }
    friend ostream& operator<<(ostream& out, const Vec& v);
};

ostream& operator<<(ostream& out, const Vec& v) {
    out << "(" << v.x << ", " << v.y << ")";
    return out;
}

int main() {
    Vec a(1, 2), b(3, 4);
    Vec c = a + b * 2;
    cout << a << " + " << b << " * 2 = " << c << endl;
    if (c == Vec(7, 10)) cout << "equal" << endl;
    return 0;
}
Open in compiler
  • Vec operator+(const Vec& o) const is a member operator: in a + b, a is the object (this) and b is the parameter o. It returns a new Vec; a and b do not change. Watch the call stack: a + b really is a call of Vec::operator+.
  • operator== returns a bool, so if (a == b) works.
  • operator<< cannot be a member of Vec, because its left side is cout, an ostream. It is a free function, declared a friend so it may read the private x and y. It returns the stream, so cout << a << endl keeps going.

a + b * 2 still follows the usual precedence: overloading changes what an operator does, never how strongly it binds.

Syntax

Vec operator+(const Vec& o) const;                   // member: a + b
bool operator==(const Vec& o) const;                 // member: a == b
friend ostream& operator<<(ostream& out, const Vec& v);
int& operator[](int i);                              // member: x[i]

Example

#include <iostream>
using namespace std;

class Marks {
    int data[3];
public:
    Marks() {
        for (int i = 0; i < 3; i++) data[i] = 0;
    }
    int& operator[](int i) {
        if (i < 0 || i >= 3) {
            cout << "bad index " << i << endl;
            return data[0];
        }
        return data[i];
    }
};

int main() {
    Marks marks;
    marks[0] = 80;
    marks[1] = 95;
    marks[5] = 1;
    cout << marks[0] + marks[1] << endl;
    return 0;
}
Open in compiler

operator[] returns int&, a reference to the element, so marks[1] = 95 can change it, not just read it. The class can also check the index, which plain arrays never do.

Write it asOperators
a member (left side is your object)= [] += -= ++ --, usually + - == < too
a free friend function<< and >> (left side is a stream)

Line by line

  1. Marks keeps three ints in a private array, and its constructor sets them to 0.
  2. operator[] checks the index. A bad index prints a message and returns data[0] so that the program can carry on.
  3. A good index returns data[i] by reference, so the caller can change it.
  4. marks[0] = 80; and marks[1] = 95; store values through the returned references.
  5. marks[5] = 1; is caught: the message bad index 5 is printed, and 1 goes into data[0].
  6. The last line adds marks[0] and marks[1], which are now 1 and 95, and prints 96.

Expected result

bad index 5
96

In the real world

The standard library uses overloading everywhere: + joins strings, << prints, [] indexes vectors and maps, and < lets sort order your objects. Maths libraries for vectors, matrices and big numbers depend on it so that formulas look like formulas.

Common mistakes

  • Changing an operand in operator+: a + b should not change a. Use operator+= for that.
  • Returning the stream by value from operator<<: return ostream&, so chains work.
  • Overloading an operator to mean something surprising (- that adds): others will misread your code.

Best practices

  • Overload an operator only when its meaning is obvious.
  • Implement + using +=, so the two always agree.
  • Pass operands by const&, and mark operators that do not change the object as const.

Summary

Operators on class types are calls of functions such as operator+ and operator<<. Members suit operators whose left side is your object, friends suit << and >>, and operator[] returns a reference so that it can be assigned to.

Quick check

1. In a + b, with Vec operator+(const Vec& o) const as a member, what is this?
2. Why is operator<< for Vec usually a free (friend) function?
3. Why does operator[] return int& instead of int?

Key takeaways

  • a + b with a class type is a call of operator+.
  • operator<< is a free function that returns the stream.
  • operator[] returns a reference so that the result can be assigned to.
  • Overloading changes what an operator does, never its precedence.

Practice problems for this lesson

My notes

Write down what you want to remember from this lesson. Notes are saved on this device as you type, and appear on your dashboard.