HomebaseWorks
Lesson 4 of 4

Copy, move and operator overloading

When the copy constructor, copy assignment and move constructor run, the rule of three, and how to overload operators without surprises.

EWritten by the HomebaseWorks editorial team · reviewed 26 Oct 2026

If a class owns a resource such as heap memory or a file handle, the compiler-generated copy does the wrong thing, and interviewers know it. This lesson shows exactly which special function runs on each line and how to write them, then covers operator overloading, which appears in the same questions.

Advertisement

Which function runs when

class Buffer {
    char* data;
    std::size_t n;
public:
    explicit Buffer(std::size_t size) : data(new char[size]), n(size) { std::cout << "ctor\n"; }
    Buffer(const Buffer& other) : data(new char[other.n]), n(other.n) {
        std::memcpy(data, other.data, n);
        std::cout << "copy\n";
    }
    Buffer& operator=(const Buffer& other) {
        if (this != &other) {
            char* fresh = new char[other.n];
            std::memcpy(fresh, other.data, other.n);
            delete[] data;
            data = fresh;
            n = other.n;
        }
        std::cout << "assign\n";
        return *this;
    }
    Buffer(Buffer&& other) noexcept : data(other.data), n(other.n) {
        other.data = nullptr;
        other.n = 0;
        std::cout << "move\n";
    }
    ~Buffer() { delete[] data; }
};

Buffer a(16);                // ctor
Buffer b = a;                // copy  (initialisation, not assignment)
b = a;                       // assign
Buffer d = std::move(a);     // move; a is now empty

Buffer b = a; calls the copy constructor because b is being created. b = a; calls copy assignment because b already exists. The move constructor steals the pointer and must leave the source in a valid, destructible state.

The rule of three and five

If you write any one of the destructor, copy constructor or copy assignment, you almost certainly need all three. Add the move constructor and move assignment to make the class cheap to return and store in containers. Better still, hold resources in std::string or std::unique_ptr so the compiler can generate all five correctly.

Operator overloading

struct Money {
    long paise;
    Money operator+(const Money& o) const { return Money{paise + o.paise}; }
    bool operator<(const Money& o) const { return paise < o.paise; }
};
std::ostream& operator<<(std::ostream& os, const Money& m) {
    return os << m.paise << " paise";
}

Money total = Money{1050} + Money{250};
std::cout << total << "\n";                  // 1300 paise

Arithmetic and comparison operators are const members that take a const reference and return a new value. operator<< must be a free function because its left operand is the stream, and it returns the stream so calls can chain. Defining operator< is what lets std::sort and std::map work with your type.

Common mistakes and a practice habit

  • Relying on the default copy for a class that owns a raw pointer, which leads to a double delete.
  • Forgetting return *this; in operator=.
  • Using the moved-from object as if it still held its data.
  • Making operator<< a member function.

Practice habit: Add a print statement to each special member function of a small class, then write ten lines that create, copy, assign, pass and return it. Predict the output before running. Repeat until you can read any such snippet and list the calls in order.

Advertisement
Continue with C++4 lessons · 1 interview set · 1 test · 1 certificate
Not tested

More in Programming