Last updated: May 2026
C++ Design Patterns: Singleton, Factory, Observer, Strategy & 12 More With Modern C++17/20 (2025)
⚡ Quick Answer: Design Patterns in C++
- 23 GoF patterns — Creational, Structural, Behavioral categories
- Singleton — Meyer's static local: thread-safe since C++11, zero-overhead
- Factory — return
unique_ptr<Base>; decouple creation from use - Observer —
std::functioncallbacks: no virtual observer base class needed - Strategy — swap algorithms at runtime; enables Open/Closed Principle
- CRTP — static polymorphism: same flexibility as virtual, zero vtable overhead
- Rule — apply patterns when complexity justifies; don't over-engineer
Design patterns are the shared vocabulary of software engineering — knowing them lets you communicate design decisions in seconds and avoid reinventing solutions that were formalized 30 years ago. This guide covers all 23 GoF patterns in context and implements 12 of them in full modern C++17/20 with annotated code, output, and the "when to use" guidance that textbooks usually skip.
🗂️
All 23 Patterns
Full GoF catalog
🏗️
Creational
Singleton, Factory, Builder
🧩
Structural
Adapter, Decorator, Proxy
🔄
Behavioral
Observer, Strategy, Command
⚡
CRTP
Static polymorphism
✨
Modern C++ Patterns
PIMPL, Policy, Type Erasure
⚠️
Anti-patterns
What to avoid
❓
FAQ / Interview
Top 10 questions
All 23 GoF Design Patterns — Quick Reference
| Pattern | Intent (one line) | Category | C++ feature |
|---|---|---|---|
| Singleton | Ensure one instance, global access | Creational | Static local var |
| Factory Method | Subclasses decide which class to instantiate | Creational | unique_ptr return |
| Abstract Factory | Create families of related objects | Creational | pure virtual factory |
| Builder | Construct complex objects step by step | Creational | Fluent API, *this |
| Prototype | Clone existing objects | Creational | virtual clone() |
| Adapter | Convert interface to another | Structural | Wrapping / MI |
| Bridge | Decouple abstraction from implementation | Structural | pImpl pointer |
| Composite | Tree structures of objects | Structural | vector<Component*> |
| Decorator | Add behavior by wrapping | Structural | RAII wrapper |
| Facade | Simplified interface to subsystem | Structural | Forwarding class |
| Flyweight | Share objects to reduce memory | Structural | shared_ptr cache |
| Proxy | Control access to an object | Structural | Same interface wrapper |
| Chain of Responsibility | Pass request along handler chain | Behavioral | Linked handlers |
| Command | Encapsulate action as object (undo/redo) | Behavioral | execute/undo methods |
| Iterator | Sequential access without exposing structure | Behavioral | STL iterators / ranges |
| Mediator | Centralize communication between objects | Behavioral | Event bus |
| Memento | Capture/restore object state | Behavioral | Snapshot struct |
| Observer | Notify dependents of state change | Behavioral | std::function vector |
| State | Change behavior when state changes | Behavioral | virtual state classes |
| Strategy | Swap interchangeable algorithms | Behavioral | std::function / vtable |
| Template Method | Define algorithm skeleton; subclasses fill steps | Behavioral | final + virtual hooks |
| Visitor | Add operations without modifying classes | Behavioral | std::variant + visit |
| Interpreter | Grammar for simple language | Behavioral | AST nodes |
Creational Patterns
singleton.cpp — Meyer's Singleton (thread-safe, C++11+)C++
#include <iostream>
#include <string>
using namespace std;
class Logger {
private:
Logger() = default; // private constructor
public:
// Delete copy and assignment — no second instance allowed
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
// Meyer's Singleton — static local init is thread-safe in C++11
static Logger& instance() {
static Logger inst; // initialized once, destroyed at exit
return inst;
}
void log(const string& msg) const {
cout << "[LOG] " << msg << endl;
}
};
int main() {
Logger::instance().log("Application started");
Logger::instance().log("Processing data");
// Same instance — address is identical
cout << (&Logger::instance() == &Logger::instance() ? "Same" : "Different") << endl;
return 0;
}
Output
[LOG] Application started
[LOG] Processing data
Same
When to use Singleton: logging, configuration, thread pools, hardware interfaces — anything that must have exactly one instance. When NOT to use it: as a convenient global variable. Singletons are global mutable state — they make unit testing hard (the state persists across tests). For most cases, prefer dependency injection: pass a shared instance to objects that need it rather than letting them fetch it themselves.
factory_method.cpp — unique_ptr-based factoryC++
#include <iostream>
#include <memory>
#include <string>
#include <stdexcept>
using namespace std;
// Abstract product
class Shape {
public:
virtual ~Shape() = default;
virtual void draw() const = 0;
virtual double area() const noexcept = 0;
};
class Circle : public Shape {
double r;
public:
explicit Circle(double radius) : r(radius) {}
void draw() const override { cout << "Circle(r=" << r << ")\n"; }
double area() const noexcept override { return 3.14159 * r * r; }
};
class Rectangle : public Shape {
double w, h;
public:
Rectangle(double w, double h) : w(w), h(h) {}
void draw() const override { cout << "Rectangle("<<w<<"x"<<h<<")\n"; }
double area() const noexcept override { return w * h; }
};
// Factory function — decouples creation from use
unique_ptr<Shape> createShape(const string& type) {
if (type == "circle") return make_unique<Circle>(5.0);
if (type == "rectangle") return make_unique<Rectangle>(4.0, 6.0);
throw invalid_argument("Unknown shape: " + type);
}
int main() {
for (const auto& type : {"circle", "rectangle"}) {
auto s = createShape(type);
s->draw();
cout << " Area: " << s->area() << "\n";
}
return 0;
}
Output
Circle(r=5)
Area: 78.5398
Rectangle(4x6)
Area: 24abstract_factory.cpp — cross-platform UI widgetsC++
#include <iostream>
#include <memory>
using namespace std;
// Abstract products
class Button { public: virtual void render() const = 0; virtual~Button()=default; };
class Checkbox{ public: virtual void render() const = 0; virtual~Checkbox()=default;};
// Windows products
class WinButton : public Button { public: void render() const override { cout << "[WinButton]\n"; } };
class WinCheckbox: public Checkbox { public: void render() const override { cout << "[WinCheckbox]\n"; } };
// Mac products
class MacButton : public Button { public: void render() const override { cout << "[MacButton]\n"; } };
class MacCheckbox: public Checkbox { public: void render() const override { cout << "[MacCheckbox]\n"; } };
// Abstract Factory
class UIFactory {
public:
virtual unique_ptr<Button> createButton() = 0;
virtual unique_ptr<Checkbox> createCheckbox() = 0;
virtual~UIFactory() = default;
};
class WinFactory : public UIFactory {
public:
unique_ptr<Button> createButton() override { return make_unique<WinButton>(); }
unique_ptr<Checkbox> createCheckbox() override { return make_unique<WinCheckbox>(); }
};
class MacFactory : public UIFactory {
public:
unique_ptr<Button> createButton() override { return make_unique<MacButton>(); }
unique_ptr<Checkbox> createCheckbox() override { return make_unique<MacCheckbox>(); }
};
void renderUI(UIFactory& factory) {
auto btn = factory.createButton();
auto chk = factory.createCheckbox();
btn->render(); chk->render();
}
int main() {
WinFactory win; renderUI(win);
MacFactory mac; renderUI(mac);
}
Output
[WinButton]
[WinCheckbox]
[MacButton]
[MacCheckbox]builder.cpp — fluent server configuration builderC++
#include <iostream>
#include <string>
using namespace std;
struct ServerConfig {
string host = "localhost";
int port = 8080;
int timeout = 30;
bool useTLS = false;
string logLevel = "info";
};
class ServerConfigBuilder {
ServerConfig cfg;
public:
// Each setter returns *this — enables chaining
ServerConfigBuilder& host (string v) { cfg.host = move(v); return *this; }
ServerConfigBuilder& port (int v) { cfg.port = v; return *this; }
ServerConfigBuilder& timeout (int v) { cfg.timeout = v; return *this; }
ServerConfigBuilder& useTLS (bool v) { cfg.useTLS = v; return *this; }
ServerConfigBuilder& logLevel(string v) { cfg.logLevel = move(v); return *this; }
ServerConfig build() const { return cfg; }
};
int main() {
auto cfg = ServerConfigBuilder()
.host("api.example.com")
.port(443)
.useTLS(true)
.timeout(60)
.logLevel("debug")
.build();
cout << cfg.host << ":" << cfg.port
<< " TLS=" << cfg.useTLS
<< " log=" << cfg.logLevel << endl;
}
Output
api.example.com:443 TLS=1 log=debugStructural Patterns: Adapter, Decorator, Proxy
Adapter — convert an incompatible interface
adapter.cpp — legacy API wrapped in modern interfaceC++
#include <iostream>
using namespace std;
// New interface clients expect
class ModernLogger {
public:
virtual ~ModernLogger() = default;
virtual void log(const string& msg) const = 0;
};
// Legacy system we cannot modify
class LegacyLogger {
public:
void writeLog(const char* text) {
cout << "[LEGACY] " << text << endl;
}
};
// Adapter: wraps Legacy, presents ModernLogger interface
class LoggerAdapter : public ModernLogger {
LegacyLogger legacy;
public:
void log(const string& msg) const override {
legacy.writeLog(msg.c_str()); // delegate + adapt
}
};
void processApp(ModernLogger& logger) {
logger.log("App started");
logger.log("Processing complete");
}
int main() {
LoggerAdapter adapter;
processApp(adapter); // uses legacy internally, modern interface externally
}
Output
[LEGACY] App started
[LEGACY] Processing completeDecorator — stack optional behaviors
decorator.cpp — coffee with optional add-onsC++
#include <iostream>
#include <memory>
using namespace std;
class Coffee {
public:
virtual ~Coffee() = default;
virtual string description() const = 0;
virtual double cost() const noexcept = 0;
};
class Espresso : public Coffee {
public:
string description() const override { return "Espresso"; }
double cost() const noexcept override { return 1.50; }
};
// Base decorator — holds and delegates to wrapped coffee
class CoffeeDecorator : public Coffee {
protected:
unique_ptr<Coffee> wrapped;
public:
explicit CoffeeDecorator(unique_ptr<Coffee> c) : wrapped(move(c)) {}
};
class MilkDecorator : public CoffeeDecorator {
public:
using CoffeeDecorator::CoffeeDecorator;
string description() const override { return wrapped->description() + ", Milk"; }
double cost() const noexcept override { return wrapped->cost() + 0.30; }
};
class SugarDecorator : public CoffeeDecorator {
public:
using CoffeeDecorator::CoffeeDecorator;
string description() const override { return wrapped->description() + ", Sugar"; }
double cost() const noexcept override { return wrapped->cost() + 0.10; }
};
int main() {
auto coffee = make_unique<Espresso>();
coffee = make_unique<MilkDecorator>(move(coffee));
coffee = make_unique<SugarDecorator>(move(coffee));
cout << coffee->description() << " → $" << coffee->cost() << endl;
}
Output
Espresso, Milk, Sugar → $1.9Behavioral Patterns: Observer, Strategy, Command, Template Method
Observer — event notification with std::function
observer.cpp — lambda-based, no virtual observer class neededC++
#include <iostream>
#include <vector>
#include <functional>
#include <string>
using namespace std;
class EventBus {
using Handler = function<void(string_view)>;
vector<Handler> handlers;
public:
// Attach any callable: lambda, function ptr, bound method
void on(Handler h) { handlers.push_back(move(h)); }
void emit(string_view event) {
for (const auto& h : handlers) h(event);
}
};
int main() {
EventBus bus;
// Observers are just lambdas — no base class required
bus.on([](string_view e){ cout << "Logger: " << e << "\n"; });
bus.on([](string_view e){ cout << "Analytics: " << e << "\n"; });
bus.on([](string_view e){ cout << "Audit: " << e << "\n"; });
bus.emit("user.login");
cout << "---\n";
bus.emit("order.placed");
}
Output
Logger: user.login
Analytics: user.login
Audit: user.login
---
Logger: order.placed
Analytics: order.placed
Audit: order.placedStrategy — swappable algorithms
strategy.cpp — sort algorithms swapped via std::functionC++
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;
class Sorter {
using SortFn = function<void(vector<int>&)>;
SortFn strategy;
public:
explicit Sorter(SortFn fn) : strategy(move(fn)) {}
void setStrategy(SortFn fn) { strategy = move(fn); }
void sort(vector<int>& v) { strategy(v); }
};
// Strategies as simple lambdas or functions
auto ascending = [](auto& v){ sort(v.begin(), v.end()); };
auto descending = [](auto& v){ sort(v.rbegin(), v.rend()); };
auto byAbsVal = [](auto& v){ sort(v.begin(), v.end(), [](int a, int b){ return abs(a) < abs(b); }); };
int main() {
Sorter sorter(ascending);
vector<int> data = {3, -1, 4, -5, 2};
sorter.sort(data);
for (int x : data) cout << x << " "; cout << "\n";
sorter.setStrategy(descending);
sorter.sort(data);
for (int x : data) cout << x << " "; cout << "\n";
}
Output
-5 -1 2 3 4
4 3 2 -1 -5 Command — undo/redo system
command.cpp — text editor with undoC++
#include <iostream>
#include <memory>
#include <stack>
#include <string>
using namespace std;
class Command {
public:
virtual ~Command() = default;
virtual void execute() = 0;
virtual void undo() = 0;
};
class TextEditor {
string text;
public:
void append(string_view s) { text += string(s); }
void deleteChars(int n) { if (n <= (int)text.size()) text.erase(text.size()-n); }
void print() const { cout << "Text: '" << text << "'\n"; }
};
class AppendCommand : public Command {
TextEditor& editor; string added;
public:
AppendCommand(TextEditor& e, string_view s) : editor(e), added(s) {}
void execute() override { editor.append(added); }
void undo() override { editor.deleteChars(added.size()); }
};
class History {
stack<unique_ptr<Command>> history;
public:
void execute(unique_ptr<Command> cmd) {
cmd->execute();
history.push(move(cmd));
}
void undo() {
if (!history.empty()) { history.top()->undo(); history.pop(); }
}
};
int main() {
TextEditor ed; History hist;
hist.execute(make_unique<AppendCommand>(ed, "Hello"));
hist.execute(make_unique<AppendCommand>(ed, ", World"));
ed.print(); // Hello, World
hist.undo(); ed.print(); // Hello
hist.undo(); ed.print(); // (empty)
}
Output
Text: 'Hello, World'
Text: 'Hello'
Text: ''CRTP — Zero-Cost Static Polymorphism
crtp.cpp — static polymorphism with mixin capabilitiesC++
#include <iostream>
using namespace std;
// CRTP base: Derived is the template parameter
template<class Derived>
class Animal {
public:
// Non-virtual: dispatches at compile time via static_cast
void makeSound() {
static_cast<Derived*>(this)->sound(); // resolved at compile time
}
// Mixin: automatically available to all Animals
void describe() {
cout << "I am a " << static_cast<Derived*>(this)->name() << "\n";
makeSound();
}
};
class Dog : public Animal<Dog> {
public:
void sound() { cout << "Woof!\n"; }
string name() { return "Dog"; }
};
class Cat : public Animal<Cat> {
public:
void sound() { cout << "Meow!\n"; }
string name() { return "Cat"; }
};
// Template function — static dispatch, no virtual table
template<class T>
void greet(Animal<T>& a) { a.describe(); }
int main() {
Dog d; greet(d);
Cat c; greet(c);
// Zero vtable overhead — equivalent to direct function calls
}
Output
I am a Dog
Woof!
I am a Cat
Meow!
CRTP vs virtual: Virtual dispatch uses a vtable lookup at runtime (~1-3 ns, tiny but measurable in tight loops). CRTP resolves to a direct function call at compile time — identical performance to non-polymorphic code. Use CRTP when: the type is known at compile time, you're writing performance-critical loops, or implementing mixins. Use virtual when: the type varies at runtime, you need to store heterogeneous objects in a container.
Modern C++ Patterns: PIMPL, Policy-Based Design, Type Erasure
pimpl.h + pimpl.cpp — compilation firewallC++
// ── widget.h (public API — only forward declaration) ────────────
#pragma once
#include <memory>
#include <string>
class Widget {
public:
Widget(const std::string& name);
~Widget(); // defined in .cpp where Impl is complete
Widget(Widget&&) noexcept;
Widget& operator=(Widget&&) noexcept;
void render() const;
private:
struct Impl; // forward declaration only
std::unique_ptr<Impl> pImpl; // pointer — sizeof(Widget) never changes
};
// ── widget.cpp (implementation — hidden from header users) ───────
#include "widget.h"
#include <iostream>
struct Widget::Impl {
std::string name;
int renderCount = 0;
// Adding members here does NOT require recompiling widget.h users
};
Widget::Widget(const std::string& n) : pImpl(std::make_unique<Impl>()) {
pImpl->name = n;
}
Widget::~Widget() = default;
Widget::Widget(Widget&&) noexcept = default;
Widget& Widget::operator=(Widget&&) noexcept = default;
void Widget::render() const {
cout << "Rendering " << pImpl->name
<< " (count=" << ++pImpl->renderCount << ")\n";
}
Anti-Patterns — What to Avoid
| Anti-Pattern | Problem | Fix |
|---|---|---|
| God Class | One class does everything — impossible to test or maintain | Split into focused, single-responsibility classes |
| Singleton overuse | Hidden global state — breaks unit tests, hides dependencies | Prefer dependency injection |
| Deep inheritance (>3 levels) | Fragile Base Class — base change breaks all descendants | Favor composition; use CRTP or interfaces |
| Raw ownership pointers | Leaks, double-free, dangling — undefined behavior | Use unique_ptr / shared_ptr |
| Virtual everything | Unnecessary vtable overhead; signals poor design | Use final for non-polymorphic classes; prefer CRTP |
| Premature patterns | Over-engineered solution before complexity justifies it | Start simple; refactor toward patterns when needed |
| Mutable global state | Action-at-a-distance bugs, non-deterministic tests | Encapsulate in objects; inject where needed |
FAQ / Interview Questions
What is the difference between Factory Method and Abstract Factory in C++?▾
Factory Method creates one product via a virtual method — each subclass creates a different concrete product. It's a single-product creational pattern. Abstract Factory creates a family of related products via multiple factory methods — all products from one factory are designed to work together (e.g., WindowsButton + WindowsCheckbox from WindowsFactory). Use Factory Method for single-product creation; use Abstract Factory when products come in families that must be consistent.
How do you implement a thread-safe Singleton in C++?▾
Use Meyer's Singleton — a static local variable:
static Singleton& getInstance() { static Singleton instance; return instance; }. Since C++11, the standard guarantees that static local variable initialization is thread-safe (magic statics). The instance is initialized exactly once, on first call, without any explicit mutex. Mark the constructor private and delete copy/move operations to prevent additional instances.What is CRTP and when should I use it instead of virtual?▾
CRTP (Curiously Recurring Template Pattern) achieves static polymorphism — the correct method is selected at compile time instead of runtime.
template<class D> class Base { void f() { static_cast<D*>(this)->impl(); } };. Use CRTP when: the concrete type is known at compile time, you're writing performance-critical inner loops, or implementing mixins (Comparable, Printable base classes). Use virtual when: types vary at runtime, you need to store heterogeneous objects in one container, or the overhead is negligible.What is the PIMPL idiom and why should I use it?▾
PIMPL (Pointer to Implementation) hides private implementation details behind a forward-declared struct pointer in the header:
struct Impl; unique_ptr<Impl> pImpl;. Benefits: (1) Compilation firewall — changing private members in Impl doesn't cause recompilation of all header users. (2) ABI stability — adding private members doesn't change the class's memory layout. (3) Clean public API — no implementation includes or private members visible in the header. Essential for library APIs and large projects where compile time matters.What is the difference between Decorator and Inheritance?▾
Inheritance adds behavior statically at compile time — you must create a new subclass for every combination of features, leading to exponential subclass explosion. Decorator adds behavior at runtime by wrapping — you create individual decorators (MilkDecorator, SugarDecorator) and stack them in any combination. Rule: use inheritance for the core "is-a" relationship; use Decorator for optional, combinable features. The Decorator is the structural pattern equivalent of the SOLID Open/Closed Principle.
When should I avoid using design patterns?▾
Avoid patterns when the problem doesn't justify the complexity they add. Signs of over-engineering: adding Singleton before you have a second-instance problem, adding Strategy when only one algorithm exists, adding Abstract Factory when you have one product family. The principle is YAGNI (You Ain't Gonna Need It) — start with the simplest code that works and refactor toward patterns when the complexity genuinely requires it. Patterns are a vocabulary for communication and a refactoring destination, not a starting point.
Master C++ design patterns with real projects
Structured lessons, 200+ exercises, completion certificate. Join 50,000+ students.