Unit 18: Operator overloading

  1. Introduction
  2. Why overload operators?
  3. Operator overloading examples
    1. Arithmetic operators
    2. Comparison operators
    3. Stream operators
    4. Subscript operator
    5. Assignment operator
  4. Other operators
  5. Best practices

  6. Example code and additional resources

This week's stuff:

Operator Overloading

Introduction

Operator Overloading is a method of overriding common C++ operators so that we can specify custom operations for a given class.

For example, perhaps you've implemented a class. By default, you won't be able to use cout or cin with your class' objects, but if you override the operator<< and operator>> functions then you can tell your program how to handle something like cout << MYOBJECT;.

We'll be overing overloading the arithmetic operators: + - * /,
the relational operators: > < >= <= == !=,
the input/output stream operators: >> << ,
the subscript operator: [ ], and the assignment operator: =.

struct Course
{
    string code;
    int    number;
};

int main()
{
    Course course;
    course.code = "CS";
    course.number = 200;

    cout << course;

    return 0;
}

Build error, since we haven't overloaded the << operator.

example.cpp:16:10: error: no match for ‘operator<<’ (operand types are ‘std::ostream’ {aka ‘std::basic_ostream<char>’} and ‘Course’)

Why overload operators?

  • Maybe you're implementing a class to be used in math (Fraction, Vector, Matrix) and you want to define how the + or * operator works with this type so people can easily write:
    matrix3 = matrix1 * matrix2
    Instead of:
    matrix3 = matrix1.product( matrix2 );

  • Maybe you've implemented a data structure, such as a LinkedList, and you want the user to be able to use the subscript operator to access an item at some index:
    my_course = course_list[2];
    Instead of:
    my_course = course_list.GetAt( 2 );

Arithmetic operators

Class declaration:

class MyClass
{
public:
  friend MyClass operator+( const MyClass& item1, const MyClass& item2 );
  friend MyClass operator-( const MyClass& item1, const MyClass& item2 );
  friend MyClass operator/( const MyClass& item1, const MyClass& item2 );
  friend MyClass operator*( const MyClass& item1, const MyClass& item2 );
};

Function implementation:

MyClass operator+( const MyClass& item1, const MyClass& item2 )
{
  MyClass sum;
  sum.memberA = item1.memberA + item2.memberA;
  return sum;
}

Notice how these are friend functions, and not member functions!

Comparison/relational operators

Class declaration:

class MyClass
{
public:
  friend bool operator==( const MyClass& item1, const MyClass& item2 );
  friend bool operator!=( const MyClass& item1, const MyClass& item2 );
  friend bool operator<( const MyClass& item1, const MyClass& item2 );
  friend bool operator<=( const MyClass& item1, const MyClass& item2 );
  friend bool operator>( const MyClass& item1, const MyClass& item2 );
  friend bool operator>=( const MyClass& item1, const MyClass& item2 );
};

Function implementation:

bool operator==( const MyClass& item1, const MyClass& item2 )
{
  return ( item1.memberA == item2.memberA );
}

Notice how these are friend functions, and not member functions!

Stream operators

Class declaration:

class MyClass
{
public:
  friend ostream& operator<<( ostream& out, MyClass& item );
  friend istream& operator>>( istream& in, MyClass& item );
};

Function implementation:

ostream& operator<<( ostream& out, MyClass& item );
{
  out << item.memberA;
  out << item.memberB;
  return out;
}

istream& operator>>( istream& in, MyClass& item );
{
  in >> item.memberA;
  in >> item.memberB;
  return in;
}

Notice how these are friend functions, and not member functions!

Subscript operator

Class declaration:

class MyClass
{
public:
  string& operator[] ( const int index );

private:
  Node* m_ptrFirst;
};

Function implementation:

string& MyClass::operator[]( const int index )
{
  // Walk to the Node at the correct index...
  Node* walker = m_ptrFirst;
  for ( int i = 0; i < index; i++ )
  {
    walker = walker->ptrNext;
  }

  // Return this Node's data...
  return walker->data;
}

Notice how this is a member function!

Assignment operator

Class declaration:

class MyClass
{
public:
  MyClass& operator=( const MyClass& other );

private:
  int a;
  float b;
};

Function implementation:

MyClass& MyClass::operator=( const MyClass& other )
{
  // MUST make sure we're not assigning something to itself!
  if ( this == &other ) { return *this; }

  // Define which member variables we want to copy over
  a = other.a;
  b = other.b;

  // Return a pointer to this object once we're done
  return *this;
}

Notice how this is a member function!

Other operators

Other operators can also be overloaded. Here is a list of all overloadable operators in C++:

These operators: + - * / % ^ & | ~ ! = < > += -= *= /= %= ^= &= |= << >> <<= >>= == != <= >= <=>(since C++20) && || ++ -- , ->* -> ( ) [ ]

* From https://en.cppreference.com/w/cpp/language/operators

Best practices

Best practices from the C++ core guidelines: https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md

🔗 F.47: Return T& from assignment operators

The convention for operator overloads (especially on concrete types) is for operator=(const T&) to perform the assignment and then return (non-const) *this. This ensures consistency with standard-library types and follows the principle of "do as the ints do."

🔗 C.60: Make copy assignment non-virtual, take the parameter by const&, and return by non-const&

It is simple and efficient. If you want to optimize for rvalues, provide an overload that takes a && (see F.18).

🔗 C.61: A copy operation should copy

That is the generally assumed semantics. After x = y, we should have x == y. After a copy x and y can be independent objects (value semantics, the way non-pointer built-in types and the standard-library types work) or refer to a shared object (pointer semantics, the way pointers work).

Example code and additional resources

Example code: (repository)

Archived videos: