The Rule of Three/Five and SDL Resource Management

What is the "Rule of Three" and why did deleting the copy operations satisfy it here? What about the Rule of Five?

The Rule of Three is a guideline in C++ that states: if a class requires a user-defined destructor, a user-defined copy constructor, OR a user-defined copy assignment operator, it almost certainly needs all three.

Why? The need for any one of these typically implies the class is managing a resource manually (like raw memory, a file handle, a network socket, or in our case, an SDL_Window*).

  • Destructor (~Window()): Needed to release the resource (e.g., SDL_DestroyWindow(SDLWindow)).
  • Copy Constructor (Window(const Window&)): The default compiler-generated copy constructor performs a member-wise copy. For a pointer like SDLWindow, this means both the original object and the new copy would end up pointing to the same SDL_Window. When one object's destructor runs (SDL_DestroyWindow), the other object is left with a dangling pointer. If the second object's destructor runs later, it tries to destroy the same window again, leading to a crash (double-free). A user-defined copy constructor would need to handle this, perhaps by creating a new SDL_Window for the copy (deep copy) or by disallowing copying.
  • Copy Assignment Operator (operator=(const Window&)): Similar issues arise with the default assignment operator. Assigning one Window to another would copy the pointer, leading to the same double-free or dangling pointer problems and potential self-assignment issues. A user-defined version needs to handle resource cleanup and proper copying.

In our Window class:

  1. We needed a destructor ~Window() to call SDL_DestroyWindow(SDLWindow).
  2. This signals manual resource management, invoking the Rule of Three.
  3. We decided that copying Window objects doesn't make sense or is complex (creating a duplicate actual window isn't usually desired).
  4. Therefore, instead of writing complex copy operations, we explicitly deleted them using = delete;.
class Window {
public:
  // Constructor acquires resource
  Window() {
    SDLWindow = SDL_CreateWindow(/* ... */);
  }

  // Destructor releases resource
  ~Window() {
    if (SDLWindow) { // Good practice: check if null
       SDL_DestroyWindow(SDLWindow);
    }
  }

  // Rule of Three: Because we defined a destructor,
  // we must consider copy operations.
  // We choose to disallow copying:
  Window(const Window&) = delete;            
  Window& operator=(const Window&) = delete; 

private:
  SDL_Window* SDLWindow{nullptr};
};

Deleting the copy operations satisfies the Rule of Three by explicitly addressing the copy behavior (by forbidding it), thus preventing the dangerous default shallow copies.

The Rule of Five (C++11 and Later)

C++11 introduced move semantics (move constructor and move assignment operator) as a way to efficiently transfer resource ownership without expensive copying. This expanded the guideline to the Rule of Five: if a class defines any of the destructor, copy constructor, copy assignment operator, move constructor, or move assignment operator, it should likely define or delete/default all five.

  • Move Constructor (Window(Window&&)): Transfers ownership of the resource (the SDL_Window*) from a temporary or expiring object to a new object.
  • Move Assignment Operator (operator=(Window&&)): Transfers ownership of the resource to an existing object, properly releasing the destination's old resource first.

For our Window class, since we deleted the copy operations, the compiler might implicitly delete the move operations too (depending on the compiler and context). However, it's best practice under the Rule of Five to be explicit if you define a destructor:

class Window {
public:
  // Constructor acquires resource
  Window() { /* ... */ }

  // Destructor releases resource
  ~Window() { /* ... */ }

  // Rule of Five: Address all five special members

  // Disallow copying
  Window(const Window&) = delete;
  Window& operator=(const Window&) = delete;

  // Decide on move semantics (e.g., default them,
  // delete them, or implement them)
  Window(Window&&) = default;
  Window& operator=(Window&&) = default;

private:
  SDL_Window* SDLWindow{nullptr};
};

In this specific case, deleting copies is sufficient to prevent the primary resource management errors. Defaulting or implementing move semantics could be done but adds complexity. Deleting copy operations is the simplest, safest approach when copying is not required.

Copy Constructors and Operators

Explore advanced techniques for managing object copying and resource allocation

Move Semantics

Learn how we can improve the performance of our types using move constructors, move assignment operators and std::move()

Creating a Window

Learn how to create and customize windows, covering initialization, window management, and rendering

Questions & Answers

Answers are generated by AI models and may not have been reviewed. Be mindful when running any code on your device.

Purpose of SDL_Quit() in SDL2
Why do we need to call SDL_Quit()? What happens if we forget?
Understanding Screen Coordinates and Pixels in SDL2
What exactly is a "screen coordinate"? Is it always the same as a pixel?
How Window Position is Determined when using SDL_WINDOWPOS_UNDEFINED
How does does SDL decide where to actually place the window when using SDL_WINDOWPOS_UNDEFINED?
Understanding SDL_PumpEvents() and the Event Loop
What does SDL_PumpEvents() actually do? Why is it in an infinite loop?
Handling the Window Close Event in SDL2
How do I make the window close properly when I click the 'X' button?
Using Raw vs. Smart Pointers for SDL Resources
Is SDL_Window* a raw pointer? Should I be using smart pointers like std::unique_ptr?
Purpose of argc and argv in SDL Applications
What are argc/argv in main for? Do I need them here?
How to Handle Window Close Events in SDL
How can I make my SDL window respond to close events, such as clicking the close button?
Using Multiple Windows in SDL
Can I create and manage multiple windows in SDL, and if so, how?
Adjusting Window Size Dynamically in SDL
How can I adjust the size of an SDL window dynamically during runtime?
Improving SDL Window Performance
What are some tips to improve the performance of an SDL window?
Or Ask your Own Question
Get an immediate answer to your specific question using our AI assistant