Inheritance & Polymorphism
Operator Overloading
Make your own types work with +, ==, <<, [] and more: member vs friend operators, unary and binary operators, and the rules you must follow.
Operator overloading lets you define what operators mean for your own classes, so code reads naturally: c1 + c2 instead of add(c1, c2). It is a form of compile-time polymorphism.
An overloaded operator is just a function named operator followed by the symbol: operator+, operator==, operator<<.
Binary operators as members
For a + b written as a member function, a is the object the function is called on and b is the parameter.
#include <iostream>
using namespace std;
class Complex {
double re, im;
public:
Complex(double r = 0, double i = 0) : re(r), im(i) {}
Complex operator+(const Complex &o) const { return Complex(re + o.re, im + o.im); }
Complex operator-(const Complex &o) const { return Complex(re - o.re, im - o.im); }
Complex operator*(const Complex &o) const {
return Complex(re * o.re - im * o.im, re * o.im + im * o.re); // (a+bi)(c+di)
}
bool operator==(const Complex &o) const { return re == o.re && im == o.im; }
void print() const { cout << re << (im < 0 ? " - " : " + ") << (im < 0 ? -im : im) << "i" << endl; }
};
int main() {
Complex a(3, 2), b(1, -4);
Complex sum = a + b; // calls a.operator+(b)
sum.print();
(a - b).print();
(a * b).print();
cout << boolalpha << (a + b == Complex(4, -2)) << endl;
return 0;
}4 - 2i 2 + 6i 11 - 10i true
Unary operators
Prefix and postfix ++ are told apart by a dummy int parameter on the postfix version:
#include <iostream>
using namespace std;
class Counter {
int value;
public:
Counter(int v = 0) : value(v) {}
Counter &operator++() { ++value; return *this; } // prefix: ++c
Counter operator++(int) { Counter old = *this; ++value; return old; } // postfix: c++
Counter operator-() const { return Counter(-value); } // unary minus
int get() const { return value; }
};
int main() {
Counter c(5);
Counter a = c++; // a gets 5, c becomes 6
Counter b = ++c; // c becomes 7, b gets 7
cout << a.get() << " " << b.get() << " " << c.get() << " " << (-c).get() << endl;
return 0;
}5 7 7 -7
Overloading << and >> with friends
cout << obj has cout (an ostream) on the left, so it cannot be a member of your class. Write it as a non-member — usually a friend so it can read private data — and return the stream to allow chaining:
#include <iostream>
using namespace std;
class Fraction {
int num, den;
static int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); }
void reduce() { int g = gcd(num < 0 ? -num : num, den); if (g) { num /= g; den /= g; } }
public:
Fraction(int n = 0, int d = 1) : num(n), den(d) { reduce(); }
Fraction operator+(const Fraction &o) const { return Fraction(num * o.den + o.num * den, den * o.den); }
friend ostream &operator<<(ostream &out, const Fraction &f) { return out << f.num << "/" << f.den; }
friend istream &operator>>(istream &in, Fraction &f) { in >> f.num >> f.den; f.reduce(); return in; }
};
int main() {
Fraction a(1, 2), b;
cout << "Enter a fraction (numerator denominator): ";
cin >> b;
cout << a << " + " << b << " = " << a + b << endl;
cout << "6/8 simplifies to " << Fraction(6, 8) << endl;
return 0;
}Enter a fraction (numerator denominator): 7 3
1/2 + 7/3 = 17/6
6/8 simplifies to 3/4Subscript and comparison operators
#include <iostream>
#include <stdexcept>
using namespace std;
class SafeArray {
int data[5] = {10, 20, 30, 40, 50};
public:
int &operator[](int i) { // returns a reference so a[i] = x works
if (i < 0 || i >= 5) throw out_of_range("index " + to_string(i));
return data[i];
}
};
int main() {
SafeArray a;
a[2] = 99;
cout << a[2] << endl;
try { cout << a[7] << endl; }
catch (const out_of_range &e) { cout << "caught bad " << e.what() << endl; }
return 0;
}99 caught bad index 7
Rules
- You cannot create new operators or change precedence, associativity or the number of operands.
- At least one operand must be a user-defined type (you cannot redefine
int + int). - These cannot be overloaded:
::..*?:sizeoftypeid. =,(),[]and->must be member functions.<<and>>for streams must be non-members (usually friends).
| Member operator | Friend / non-member operator |
|---|---|
left operand is *this |
both operands are parameters |
| one parameter for binary operators | two parameters for binary operators |
| left operand must be your class | left operand can be any type (cout << x, 5 + x) |
Test yourself
4 questions · pick an answer to check it instantly
- 1
How many parameters does a member
operator+take?The left operand is the object itself (this); the right operand is the single parameter. - 2
Why must
operator<<forcout << objbe a non-member?A member operator always has the class object on the left. - 3
Which operator cannot be overloaded?
Scope resolution, member access.,?:andsizeofcannot be overloaded. - 4
How does C++ distinguish postfix
operator++from prefix?operator++(int)is postfix;operator++()is prefix.