Design Patterns in C++

Learn classic design patterns implemented in modern C++. Explore Singleton, Observer, Factory, and Strategy patterns with interactive examples.

Best viewed on desktop for optimal interactive experience

Design Patterns in C++

Design patterns are reusable solutions to common problems in software design. They represent best practices developed over time by experienced developers. In C++, these patterns can be implemented efficiently using modern language features.

Pattern Categories

  1. Creational: Object creation mechanisms
  2. Structural: Object composition
  3. Behavioral: Communication between objects

Design Pattern Categories

Creational

Object creation mechanisms

  • • Singleton, Factory
  • • Builder, Prototype
  • • Abstract Factory

Structural

Object composition

  • • Adapter, Decorator
  • • Facade, Proxy
  • • Composite, Bridge

Behavioral

Communication between objects

  • • Observer, Strategy
  • • Command, State
  • • Visitor, Iterator

Interactive Pattern Explorer

Singleton Pattern

Thread-Safe Singleton (C++11)
class Singleton {
private:
    Singleton() = default;
    
public:
    // Delete copy constructor and assignment
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    
    static Singleton& getInstance() {
        static Singleton instance;  // Thread-safe in C++11
        return instance;
    }
    
    void doSomething() {
        std::cout << "Doing something...
";
    }
};

// Usage
Singleton::getInstance().doSomething();
Meyer's Singleton Benefits
  • Thread-safe initialization
  • Lazy initialization
  • Automatic destruction
  • No dynamic allocation
Consider if you really need a singleton. Often dependency injection is better!

Modern C++ Pattern Evolution

Modern C++ Enhancements:
  • • Use smart pointers instead of raw pointers in patterns
  • • Leverage std::function for strategy pattern alternatives
  • • Apply move semantics for better performance
  • • Consider template-based solutions (CRTP, policy-based design)

Traditional vs Modern

// Traditional Strategy
class Strategy {
public:
    virtual ~Strategy() = default;
    virtual void execute() = 0;
};

// Modern with std::function
class Context {
    std::function<void()> strategy;
public:
    void setStrategy(std::function<void()> s) {
        strategy = std::move(s);
    }
};

Template-Based Patterns

// CRTP (Curiously Recurring Template Pattern)
template<typename Derived>
class Base {
public:
    void interface() {
        static_cast<Derived*>(this)->implementation();
    }
};

class Concrete : public Base<Concrete> {
public:
    void implementation() {
        // Implementation
    }
};

Singleton Pattern

Intent

Ensure a class has only one instance and provide global access to it.

Modern C++ Implementation

class Singleton { private: Singleton() = default; public: // Delete copy constructor and assignment Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; static Singleton& getInstance() { static Singleton instance; // Thread-safe in C++11+ return instance; } void doSomething() { // Implementation } };

Benefits of Meyer's Singleton

  • Thread-safe initialization (C++11+)
  • Lazy initialization
  • Automatic destruction
  • No dynamic allocation

When to Use

  • Logging systems
  • Configuration managers
  • Hardware interface abstractions
  • Caution: Often overused; consider dependency injection

Observer Pattern

Intent

Define a one-to-many dependency between objects so that when one object changes state, all dependents are notified.

Implementation

class Observer { public: virtual ~Observer() = default; virtual void update(const std::string& message) = 0; }; class Subject { private: std::vector<Observer*> observers; public: void attach(Observer* observer) { observers.push_back(observer); } void detach(Observer* observer) { observers.erase( std::remove(observers.begin(), observers.end(), observer), observers.end() ); } void notify(const std::string& message) { for (auto* observer : observers) { observer->update(message); } } };

Modern C++ Enhancements

  • Use std::function for callbacks
  • Consider std::weak_ptr to avoid dangling pointers
  • Event systems with std::signal libraries

Factory Pattern

Intent

Create objects without specifying their exact classes.

Implementation

// Abstract Product class Shape { public: virtual ~Shape() = default; virtual void draw() const = 0; virtual double area() const = 0; }; // Concrete Products class Circle : public Shape { double radius; public: Circle(double r) : radius(r) {} void draw() const override { /* Implementation */ } double area() const override { return 3.14159 * radius * radius; } }; // Factory class ShapeFactory { public: enum ShapeType { CIRCLE, RECTANGLE }; static std::unique_ptr<Shape> createShape( ShapeType type, const std::vector<double>& params) { switch (type) { case CIRCLE: if (params.size() >= 1) return std::make_unique<Circle>(params[0]); break; case RECTANGLE: if (params.size() >= 2) return std::make_unique<Rectangle>(params[0], params[1]); break; } return nullptr; } };

Benefits

  • Decouples object creation from usage
  • Makes code more flexible and testable
  • Supports polymorphism naturally

Strategy Pattern

Intent

Define a family of algorithms, encapsulate each one, and make them interchangeable.

Implementation

// Strategy interface class SortingStrategy { public: virtual ~SortingStrategy() = default; virtual void sort(std::vector<int>& data) = 0; virtual std::string getName() const = 0; }; // Concrete strategies class BubbleSort : public SortingStrategy { public: void sort(std::vector<int>& data) override { // Bubble sort implementation for (size_t i = 0; i < data.size(); ++i) { for (size_t j = 0; j < data.size() - 1 - i; ++j) { if (data[j] > data[j + 1]) { std::swap(data[j], data[j + 1]); } } } } std::string getName() const override { return "Bubble Sort"; } }; // Context class Sorter { private: std::unique_ptr<SortingStrategy> strategy; public: void setStrategy(std::unique_ptr<SortingStrategy> newStrategy) { strategy = std::move(newStrategy); } void sort(std::vector<int>& data) { if (strategy) { strategy->sort(data); } } };

Modern Alternative with std::function

class ModernSorter { private: std::function<void(std::vector<int>&)> strategy; public: void setStrategy(std::function<void(std::vector<int>&)> s) { strategy = std::move(s); } void sort(std::vector<int>& data) { if (strategy) strategy(data); } };

RAII and Design Patterns

Many C++ design patterns benefit from RAII:

Lock Guard Pattern

class LockGuard { std::mutex& m; public: LockGuard(std::mutex& mutex) : m(mutex) { m.lock(); } ~LockGuard() { m.unlock(); } };

Scope Guard Pattern

template<typename F> class ScopeGuard { F cleanup; bool active; public: ScopeGuard(F f) : cleanup(std::move(f)), active(true) {} ~ScopeGuard() { if (active) cleanup(); } void dismiss() { active = false; } };

Modern C++ Pattern Evolution

Template-Based Patterns

  • Policy-based design
  • CRTP (Curiously Recurring Template Pattern)
  • Expression templates

Functional Patterns

  • Higher-order functions with lambdas
  • Monadic patterns with optional/expected
  • Visitor pattern with std::variant

Best Practices

  1. Prefer composition over inheritance
  2. Use modern C++ features (smart pointers, lambdas)
  3. Consider alternatives before applying patterns
  4. Keep it simple - don't overengineer
  5. Document pattern usage clearly

Design patterns in C++ should leverage the language's strengths while providing clear, maintainable solutions to common problems.

If you found this explanation helpful, consider sharing it with others.

Mastodon