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
- Creational: Object creation mechanisms
- Structural: Object composition
- 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
Modern C++ Pattern Evolution
- • 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
- Prefer composition over inheritance
- Use modern C++ features (smart pointers, lambdas)
- Consider alternatives before applying patterns
- Keep it simple - don't overengineer
- Document pattern usage clearly
Design patterns in C++ should leverage the language's strengths while providing clear, maintainable solutions to common problems.