Structuring SDL Programs

The virtual Keyword and Polymorphism in C++

What does the virtual keyword do, and why might I need it for polymorphism in SDL UI components?

Abstract art representing computer programming

The virtual keyword in C++ is the key that unlocks dynamic dispatch, which is essential for polymorphism when working with pointers or references to base classes.

Static vs. Dynamic Dispatch

Consider a simple function call like myObject.SomeFunction();. When the compiler sees this, it knows the exact type of myObject at compile time. It can directly determine which function (MyClass::SomeFunction) to call. This is called static dispatch or early binding.

Now, consider the scenario from the lesson:

// Assume Rectangle has Render()
// Assume GreenRectangle inherits Rectangle and
// potentially has its own Render()
std::vector<std::unique_ptr<Rectangle>> components;
components.push_back(std::make_unique<Rectangle>(...));
components.push_back(
  std::make_unique<GreenRectangle>(...)
);

for (const auto& ptr : components) {
  ptr->Render(surface); // Which Render() is called?
}

Here, ptr is always of type std::unique_ptr<Rectangle>, giving access via a Rectangle*. At compile time, the compiler only knows it's calling Render through a Rectangle pointer. How does it know whether to call the original Rectangle::Render or the specific GreenRectangle::Render for the second element?

Enabling Dynamic Dispatch with virtual

This is where virtual comes in. When you declare a function as virtual in a base class, you tell the compiler: "The decision of which version of this function to call should be made at runtime, based on the actual type of the object being pointed to, not just the type of the pointer." This is dynamic dispatch or late binding.

// Rectangle.h
#pragma once
#include <SDL.h>
#include <iostream> // For std::cout example

class Rectangle {
 // ... other members ...
 public:
  Rectangle(const SDL_Rect& Rect) : Rect{Rect} {}

  // Declare Render as virtual
  virtual void Render(SDL_Surface* Surface) const { 
    auto [r, g, b, a]{
      isPointerHovering ? HoverColor : Color
    };
    SDL_FillRect(
      Surface, &Rect,
      SDL_MapRGB(Surface->format, r, g, b)
    );
  }

  // Declare HandleEvent as virtual
  virtual void HandleEvent(SDL_Event& E) { 
    if (E.type == SDL_MOUSEMOTION) {
      isPointerHovering = isWithinRect(
        E.motion.x, E.motion.y
      );
    } else if (
      E.type == SDL_WINDOWEVENT &&
      E.window.event == SDL_WINDOWEVENT_LEAVE
    ) {
      isPointerHovering = false;
    } else if (E.type == SDL_MOUSEBUTTONDOWN) {
      if (isPointerHovering &&
        E.button.button == SDL_BUTTON_LEFT
      ) {
        std::cout << "Rectangle clicked!\\n";
      }
    }
  }

  // IMPORTANT: Base classes with virtual functions
  // should almost always have a virtual destructor
  virtual ~Rectangle() = default; 

 // ... private members, isWithinRect ...
 private:
  SDL_Rect Rect;
  SDL_Color Color{255, 0, 0};
  SDL_Color HoverColor{0, 0, 255};
  bool isPointerHovering{false};
  // ... isWithinRect implementation ...
};
// GreenRectangle.h
#pragma once
#include "Rectangle.h"

class GreenRectangle : public Rectangle {
 public:
  GreenRectangle(const SDL_Rect& Rect)
  : Rectangle{Rect} {
    SetColor({0, 255, 0});
    SetHoverColor({0, 180, 0});
  }

  // Override the virtual function (optional 'override' keyword helps)
  // No need to repeat 'virtual' here, but good practice
  // to add 'override' for clarity and compiler checks.
  void HandleEvent(SDL_Event& E) override { 
    // Call the base class version first, if desired
    // Rectangle::HandleEvent(E);

    // Or provide completely new behavior
    if (E.type == SDL_MOUSEMOTION) {
      // Maybe GreenRectangles don't change color on hover
      // isPointerHovering = isWithinRect(...); // Base logic
    } else if (E.type == SDL_MOUSEBUTTONDOWN) {
      // Check isPointerHovering (inherited private member access
      // would need protected or accessor function in base)
      // For simplicity, let's assume we can check hover state
      // if (isPointerHovering && ...) {
      // Direct call needs isWithinRect to be public/protected
      if (isWithinRect(E.button.x, E.button.y) && 
          E.button.button == SDL_BUTTON_LEFT) {
        std::cout << "GreenRectangle clicked!\\n";
      }
    }
    // Let base class handle other events if needed
    // (This shows a mix; often you fully override or call base)
  }

  // Destructor is implicitly virtual because base is virtual
  // ~GreenRectangle() override = default; // Optional but clear
};

Now, when the loop calls ptr->Render(surface) or ptr->HandleEvent(E), the program checks the actual object type at runtime. If ptr points to a GreenRectangle, GreenRectangle::HandleEvent will be executed (if overridden). If it points to a base Rectangle, Rectangle::HandleEvent runs.

Why Needed Here?

In our SDL UI structure using std::vector<std::unique_ptr<Rectangle>>, we need virtual functions for Render and HandleEvent. Without virtual, the loop calling ptr->Render() would always call Rectangle::Render, even if the pointer actually pointed to a GreenRectangle object. The specific behaviors or appearances defined in GreenRectangle would be ignored when accessed through the base pointer.

Virtual Destructor

Crucially, if a class has any virtual functions, it should almost always have a virtual destructor, even if it's empty or defaulted (virtual ~Rectangle() = default;).

When you delete a derived object through a base class pointer (which std::unique_ptr does automatically when it goes out of scope or is reset), having a virtual destructor ensures that the derived class's destructor is called first, followed by the base class's destructor.

Without it, only the base destructor might run, potentially leading to resource leaks if the derived class managed resources.

This Question is from the Lesson:

Structuring SDL Programs

Discover how to organize SDL components using manager classes, inheritance, and polymorphism for cleaner code.

Answers to questions are automatically generated and may not have been reviewed.

This Question is from the Lesson:

Structuring SDL Programs

Discover how to organize SDL components using manager classes, inheritance, and polymorphism for cleaner code.

sdl2-promo.jpg
Part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

This course includes:

  • 110 Lessons
  • 92% Positive Reviews
  • Regularly Updated
  • Help and FAQs
Free, Unlimited Access

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Screenshot from Warhammer: Total War
Screenshot from Tomb Raider
Screenshot from Jedi: Fallen Order
Contact|Privacy Policy|Terms of Use
Copyright © 2025 - All Rights Reserved