Advanced 14 min

Rule of Three/Five

Master the Rule of Three/Five for complete resource management in C++ classes

Learn the Rule of Three/Five - the foundation of correct resource management in C++ through special member functions.

A Simple Example

#include <iostream>
#include <utility>

class Resource {
private:
    int* data;
    int size;

public:
    Resource(int s) : size{s} {
        data = new int[size];
        std::cout << "Constructor (" << size << " ints)" << "\n";
    }

    ~Resource() {
        std::cout << "Destructor" << "\n";
        delete[] data;
    }

    Resource(const Resource& other) : size{other.size} {
        data = new int[size];
        for (int i{0}; i < size; ++i) {
            data[i] = other.data[i];
        }
        std::cout << "Copy constructor" << "\n";
    }

    Resource& operator=(const Resource& other) {
        if (this != &other) {
            delete[] data;
            size = other.size;
            data = new int[size];
            for (int i{0}; i < size; ++i) {
                data[i] = other.data[i];
            }
            std::cout << "Copy assignment" << "\n";
        }
        return *this;
    }

    Resource(Resource&& other) noexcept : data{other.data}, size{other.size} {
        other.data = nullptr;
        other.size = 0;
        std::cout << "Move constructor" << "\n";
    }

    Resource& operator=(Resource&& other) noexcept {
        if (this != &other) {
            delete[] data;
            data = other.data;
            size = other.size;
            other.data = nullptr;
            other.size = 0;
            std::cout << "Move assignment" << "\n";
        }
        return *this;
    }

    void display() const {
        std::cout << "Resource with " << size << " ints" << "\n";
    }
};

int main() {
    Resource r1{100};

    Resource r2 = r1;

    Resource r3{200};
    r3 = r1;

    Resource r4 = std::move(r1);

    Resource r5{300};
    r5 = std::move(r2);

    return 0;
}

Breaking It Down

The Rule of Three

  • Three special members: Destructor, copy constructor, copy assignment operator
  • Rule: If you define one, you probably need all three
  • Why: They work together to manage resource ownership
  • Remember: Rule of Three is for C++98/03 and basic resource management

The Rule of Five

  • Adds two more: Move constructor and move assignment operator
  • Why: Enables efficient transfer of resources without copying
  • When: C++11 and later, for performance-critical code
  • Remember: Move operations should be noexcept for optimal performance

Destructor

  • Purpose: Release resources when object is destroyed
  • Example: delete[] data; frees allocated memory
  • Always called: Automatically when object goes out of scope
  • Remember: Critical for preventing resource leaks

Copy Operations

  • Copy constructor: Resource(const Resource& other) - deep copy for initialization
  • Copy assignment: Resource& operator=(const Resource& other) - deep copy for existing object
  • Both need: Self-assignment check, deep copy of resources, return *this
  • Remember: Copying is expensive but necessary for safety

Move Operations

  • Move constructor: Resource(Resource&& other) noexcept - steal resources
  • Move assignment: Resource& operator=(Resource&& other) noexcept - steal and cleanup
  • Both need: Transfer ownership, leave source in valid but empty state, noexcept
  • Remember: Moving is cheap - just pointer swapping, no deep copy

Why This Matters

  • The Rule of Three/Five isn't just a guideline - it's the foundation of correct resource management in C++.
  • If your class manages a resource (memory, files, locks), you need these special member functions working together correctly. Get it wrong and you have memory leaks, double deletions, or crashes. Get it right and you have robust, efficient code.
  • This is one of C++'s most important idioms, distinguishing C++ from garbage-collected languages.

Critical Insight

You can opt out! If your class doesn't manage resources directly, you don't need the Rule of Three/Five - the compiler-generated defaults are perfect. This is the "Rule of Zero" - prefer using smart pointers and STL containers that handle resources for you. Only implement special member functions when you're managing raw resources:

// Rule of Five - manual resource management
class Manual {
    int* data;
    // Need all 5 special functions
};

// Rule of Zero - let standard library handle it
class Modern {
    std::unique_ptr<int[]> data;  // Handles everything!
    // Compiler-generated functions work perfectly
};

Best Practices

Prefer Rule of Zero: Use smart pointers and STL containers instead of manual resource management when possible.

Mark move operations noexcept: This enables optimizations in STL containers and ensures strong exception safety.

Always check self-assignment: In copy and move assignment operators, check if (this != &other) first.

Leave moved-from objects valid: After move, the source object should be in a valid but unspecified state (usually empty).

Common Mistakes

Forgetting copy assignment: Having destructor and copy constructor but not copy assignment causes bugs.

Not marking move noexcept: Without noexcept, STL containers can't use move operations optimally.

Invalid moved-from objects: After moving, source object must still be destructible - don't leave dangling pointers.

Mixing manual and automatic: Using both raw pointers and smart pointers in the same class is error-prone.

Debug Challenge

This class is missing a critical function. Click the highlighted line to add it:

1 class Array {
2 int* data;
3 int size;
4 public:
5 Array(int s) : size{s}, data{new int[s]} { }
6 ~Array() { delete[] data; }
7 Array(const Array& other) : size{other.size}, data{new int[size]} {
8 for(int i{0}; i<size; ++i) data[i] = other.data[i];
9 }
10 };

Quick Quiz

  1. What are the three functions in the Rule of Three?
Destructor, copy constructor, copy assignment operator
Constructor, destructor, copy constructor
Move constructor, move assignment, destructor
  1. What does the Rule of Five add to the Rule of Three?
Move constructor and move assignment operator
Default constructor and parameterized constructor
Virtual destructor and pure virtual function
  1. When can you use the Rule of Zero?
When using smart pointers and containers that manage resources automatically
Never, always implement all five
Only in C++98

Practice Playground

Time to try out what you just learned! Play with the example code below, experiment by making changes and running the code to deepen your understanding.

Lesson Progress

  • Fix This Code
  • Quick Quiz
  • Practice Playground - run once