xSKILLby Infirals
Register

C++ for Beginners

0 of 24 notes read 0%

Reading progress is saved to your INFIRALS account. Sign in to keep this note marked as read.

Operator overloading

Inheritance, operators, and templates13 min readLesson 4 of 5

4.1 Operators are functions

An operator overload is a function whose name is operator followed by the symbol. a + b calls operator+(a, b) when either operand has a class type. The compiler still applies the built-in precedence and arity, so overloading only chooses which function runs.

An operator can be a member function or a free function. A member operator+ receives the right operand as its single parameter and the left operand as this. A free function receives both operands. At least one operand must be a class or enumeration type, and operators such as ::, ., ?:, and sizeof cannot be overloaded.

cpp
#include <iostream>
struct Vec2 {
    double x = 0.0;
    double y = 0.0;
};
Vec2 operator+(const Vec2& a, const Vec2& b) {
    return Vec2{a.x + b.x, a.y + b.y};
}
bool operator==(const Vec2& a, const Vec2& b) {
    return a.x == b.x && a.y == b.y;
}
int main() {
    Vec2 a{1.0, 2.0};
    Vec2 b{3.0, 4.0};
    Vec2 c = a + b;
    std::cout << "(" << c.x << ", " << c.y << ")\n";
    std::cout << std::boolalpha << "Equal: " << (a == b) << "\n";
    return 0;
}

Output

(4, 6)
Equal: false

4.1.1 Writing to a stream

std::cout << value calls operator<<(std::cout, value). The left operand is a stream, so this operator is a free function. It takes std::ostream& and the value, writes the value, and returns the stream so insertions can chain.

A stream operator should not print a newline unless the type's documented format includes one. Callers decide how lines are arranged. If the operator needs private data, declare it a friend or provide const getters. A const reference parameter keeps the object from being copied and prevents the operator from changing it.

cpp
#include <iostream>

struct Vec2 {
    double x = 0.0;
    double y = 0.0;
};
std::ostream& operator<<(std::ostream& out, const Vec2& v) {
    out << "(" << v.x << ", " << v.y << ")";
    return out;
}
int main() {
    Vec2 a{1.0, 2.0};
    std::cout << "Point: " << a << "\n";
    return 0;
}

Output

Point: (1, 2)

4.1.2 Compound assignment and arithmetic

operator+= changes the left object and returns a reference to it. Returning *this by reference matches the behavior of the built-in += and allows chained updates. operator+ can then take its left operand by value, add the right operand, and return the result.

This pairing keeps the arithmetic logic in one place. It also makes clear which operation mutates and which one produces a new value. If a type supports ==, decide whether != should also exist and implement it in terms of == so the two never disagree.

cpp
#include <iostream>

class Counter {
public:
    explicit Counter(int value = 0) : value_(value) {}
    Counter& operator+=(int amount) { value_ += amount; return *this; }
    int value() const { return value_; }
private:
    int value_;
};
Counter operator+(Counter left, int amount) { left += amount; return left; }
int main() {
    Counter count(5);
    count += 3;
    Counter total = count + 2;
    std::cout << "Count: " << count.value() << "\nTotal: " << total.value() << "\n";
    return 0;
}

Output

Count: 8
Total: 10

4.1.3 Ordering objects

std::sort and ordered containers use operator< by default. The comparison must define a strict weak ordering: an object never compares less than itself, and the relation is transitive. A comparison that treats two different objects as neither less than the other is allowed, and it makes them equivalent for sorting.

When several fields matter, compare the most important field first and fall through to the next field for ties. That gives a stable, predictable order. Define the operator as a free function taking two const references so it works with either operand on the left.

cpp
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
struct Student {
    std::string name;
    int marks = 0;
};
bool operator<(const Student& a, const Student& b) {
    if (a.marks != b.marks) return a.marks < b.marks;
    return a.name < b.name;
}
int main() {
    std::vector<Student> list = {{"Ravi", 72}, {"Ada", 90}, {"Mei", 72}};
    std::sort(list.begin(), list.end());
    for (const Student& s : list) {
        std::cout << s.name << " " << s.marks << "\n";
    }
    return 0;
}

Output

Mei 72
Ravi 72
Ada 90

4.1.4 Assignment operators

The compiler generates a copy assignment operator that assigns each member. For a class that owns a resource, that default can copy a pointer and leave two objects owning the same memory. A user-defined operator= should release the old resource, copy the new state, and return *this.

Guard against self-assignment when the operation would destroy the source before reading it. if (this == &other) return *this; is the usual check. A class that needs a destructor, a copy constructor, and a copy assignment operator usually needs all three, a guideline known as the rule of three.

4.1.5 Guidelines

Overload an operator when the symbol has an obvious meaning for the type. Vector addition, string concatenation, and stream output all fit. Avoid overloading &&, ||, and the comma operator, because the overload loses short-circuit behavior or the built-in sequencing that callers expect.

Keep related operators consistent. If a == b is true, a != b should be false. If a < b is true, b < a should be false. A conversion operator should usually be explicit so the compiler does not convert a type in surprising places.

4.1.6 Common mistakes

  • Defining operator+ as a member when the left operand is a stream.
  • Returning void from operator<<, which stops chained output.
  • Returning a value from operator+= instead of a reference.
  • Omitting a field from operator<, which makes two different objects compare equal.
  • Writing an assignment operator that leaks the old resource or fails on self-assignment.
  • Overloading an operator with a meaning that differs from the built-in one.

4.1.7 Practice

  1. Add operator- to Vec2 and print the result.
  2. Write operator<< for a Counter and use it in a chained output statement.
  3. Add operator== and operator!= for Student so both compare marks and names.
  4. Sort a vector of Student by name instead of marks.
  5. Explain why operator<< is a free function rather than a member of Vec2.

Interview questions

3 questions from this note. Open one to see the answer; no account needed.

  • 01Does operator overloading change how the operator parses or its precedence?

    No. The compiler still parses the expression with the built-in precedence and arity. The overload only supplies the function that runs for operands of the class type.

  • 02Why is `operator<<` usually a free function?

    Its left operand is `std::ostream`, which is not your class, so a member function of your class cannot be called with the stream on the left. A free function taking `std::ostream&` and your type works and returns the stream for chaining.

  • 03What does `operator+=` return, and why?

    It returns a reference to the left operand, usually `*this`, so expressions like `a += b += c` work and the result refers to the original object. Returning by value would copy the object.