Menu
Coddy logo textTech

Rule of Three / Five / Zero

Part of the Object Oriented Programming section of Coddy's C++ journey — lesson 25 of 104.

When your class manages resources like dynamic memory, you've learned that you need a custom destructor, copy constructor, and move constructor. But there's a guiding principle that helps you decide which special member functions to implement: the Rule of Three, Five, and Zero.

The Rule of Three states: if you define any of these three, you should define all three:

  • Destructor
  • Copy constructor
  • Copy assignment operator

The Rule of Five extends this for modern C++, adding move operations:

  • Destructor
  • Copy constructor
  • Copy assignment operator
  • Move constructor
  • Move assignment operator
class Buffer {
    int* data;
    size_t size;
public:
    Buffer(size_t s) : size(s), data(new int[s]) {}
    
    ~Buffer() { delete[] data; }                          // 1. Destructor
    Buffer(const Buffer& other);                          // 2. Copy constructor
    Buffer& operator=(const Buffer& other);               // 3. Copy assignment
    Buffer(Buffer&& other) noexcept;                      // 4. Move constructor
    Buffer& operator=(Buffer&& other) noexcept;           // 5. Move assignment
};

The Rule of Zero is the simplest approach: if your class doesn't directly manage resources, don't define any of these functions. Let the compiler generate them, or use smart pointers and standard containers that handle resources for you.

class Player {
    std::string name;              // std::string manages its own memory
    std::vector<int> scores;       // std::vector handles its resources
public:
    Player(std::string n) : name(n) {}
    // No destructor, copy, or move functions needed!
};

Following these rules prevents bugs like double-deletion, memory leaks, and dangling pointers that occur when some operations are defined but others are missing.

challenge icon

Challenge

Easy

Let's build a TextBuffer class that follows the Rule of Five — implementing all five special member functions to properly manage dynamically allocated character data. This will demonstrate how copy and move operations work together to create a robust, resource-managing class.

You'll create two files to organize your code:

  • TextBuffer.h: Define a TextBuffer class that stores text in a dynamically allocated character array. Your class needs:
    • Private members: a char* pointer called data for the text content, and a size_t length for the string length (not including null terminator)
    • A parameterized constructor that takes a C-string (const char*), allocates memory, copies the content, and prints "TextBuffer created: <text>"
    • A destructor that frees memory (if not null) and prints "TextBuffer destroyed"
    • A copy constructor that performs a deep copy and prints "TextBuffer copied"
    • A copy assignment operator that handles self-assignment, cleans up existing data, performs a deep copy, and prints "TextBuffer copy-assigned". Return *this
    • A move constructor (marked noexcept) that transfers ownership and prints "TextBuffer moved". Leave the source in a valid empty state
    • A move assignment operator (marked noexcept) that handles self-assignment, cleans up existing data, transfers ownership, and prints "TextBuffer move-assigned". Return *this
    • A getText() method that returns the stored text (return empty string "" if data is null)
    • A getLength() method that returns the length
  • main.cpp: Demonstrate all five special member functions in action. Read a text string from input, then:
    • Create a TextBuffer called original with the input text
    • Create copied using the copy constructor from original
    • Create another with the text "Temporary"
    • Use copy assignment: another = original
    • Create moved by move-constructing from original using std::move()
    • Create target with the text "Target"
    • Use move assignment: target = std::move(copied)
    • Print "--- Final State ---"
    • Print "original: <text> (length: <len>)" for each buffer: original, copied, moved, another, target

After moves, the source objects (original and copied) should show empty text with length 0, while the destination objects hold the transferred data. This demonstrates the Rule of Five in action — all five functions working together to ensure safe resource management.

Include <cstring> for string functions like strlen and strcpy, and <utility> for std::move().

Cheat sheet

The Rule of Three states that if you define any of these three special member functions, you should define all three:

  • Destructor
  • Copy constructor
  • Copy assignment operator

The Rule of Five extends this for modern C++ by adding move operations:

  • Destructor
  • Copy constructor
  • Copy assignment operator
  • Move constructor
  • Move assignment operator
class Buffer {
    int* data;
    size_t size;
public:
    Buffer(size_t s) : size(s), data(new int[s]) {}
    
    ~Buffer() { delete[] data; }                          // 1. Destructor
    Buffer(const Buffer& other);                          // 2. Copy constructor
    Buffer& operator=(const Buffer& other);               // 3. Copy assignment
    Buffer(Buffer&& other) noexcept;                      // 4. Move constructor
    Buffer& operator=(Buffer&& other) noexcept;           // 5. Move assignment
};

The Rule of Zero states that if your class doesn't directly manage resources, don't define any special member functions. Use smart pointers and standard containers that handle resources automatically:

class Player {
    std::string name;              // std::string manages its own memory
    std::vector<int> scores;       // std::vector handles its resources
public:
    Player(std::string n) : name(n) {}
    // No destructor, copy, or move functions needed!
};

Following these rules prevents bugs like double-deletion, memory leaks, and dangling pointers.

Try it yourself

#include <iostream>
#include <string>
#include <utility>
#include "TextBuffer.h"

using namespace std;

int main() {
    string input;
    getline(cin, input);

    // TODO: Create a TextBuffer called 'original' with the input text

    // TODO: Create 'copied' using the copy constructor from 'original'

    // TODO: Create 'another' with the text "Temporary"

    // TODO: Use copy assignment: another = original

    // TODO: Create 'moved' by move-constructing from 'original' using std::move()

    // TODO: Create 'target' with the text "Target"

    // TODO: Use move assignment: target = std::move(copied)

    // TODO: Print "--- Final State ---"

    // TODO: Print the state of each buffer in this format:
    // "original: <text> (length: <len>)"
    // Print for: original, copied, moved, another, target

    return 0;
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming