Last updated: May 2026

C++ Design Patterns: Singleton, Factory, Observer, Strategy & 12 More With Modern C++17/20 (2025)

By CoodeVerse Editorial Team ✓ 2026 Verified ⏱ 25 min read 🎯 Intermediate–Advanced 📦 C++17/20
Difficulty:
Intermediate–Advanced — Prerequisites: Classes & Objects, Advanced OOP

⚡ Quick Answer: Design Patterns in C++

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

🗂️ Section 1

All 23 GoF Design Patterns — Quick Reference

PatternIntent (one line)CategoryC++ feature
SingletonEnsure one instance, global accessCreationalStatic local var
Factory MethodSubclasses decide which class to instantiateCreationalunique_ptr return
Abstract FactoryCreate families of related objectsCreationalpure virtual factory
BuilderConstruct complex objects step by stepCreationalFluent API, *this
PrototypeClone existing objectsCreationalvirtual clone()
AdapterConvert interface to anotherStructuralWrapping / MI
BridgeDecouple abstraction from implementationStructuralpImpl pointer
CompositeTree structures of objectsStructuralvector<Component*>
DecoratorAdd behavior by wrappingStructuralRAII wrapper
FacadeSimplified interface to subsystemStructuralForwarding class
FlyweightShare objects to reduce memoryStructuralshared_ptr cache
ProxyControl access to an objectStructuralSame interface wrapper
Chain of ResponsibilityPass request along handler chainBehavioralLinked handlers
CommandEncapsulate action as object (undo/redo)Behavioralexecute/undo methods
IteratorSequential access without exposing structureBehavioralSTL iterators / ranges
MediatorCentralize communication between objectsBehavioralEvent bus
MementoCapture/restore object stateBehavioralSnapshot struct
ObserverNotify dependents of state changeBehavioralstd::function vector
StateChange behavior when state changesBehavioralvirtual state classes
StrategySwap interchangeable algorithmsBehavioralstd::function / vtable
Template MethodDefine algorithm skeleton; subclasses fill stepsBehavioralfinal + virtual hooks
VisitorAdd operations without modifying classesBehavioralstd::variant + visit
InterpreterGrammar for simple languageBehavioralAST nodes
🏗️ Section 2

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: 24
abstract_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=debug
🧩 Section 3

Structural 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 complete

Decorator — 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.9
🔄 Section 4

Behavioral 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.placed

Strategy — 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: ''
⚡ Section 5

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.
✨ Section 6

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";
}
⚠️ Section 7

Anti-Patterns — What to Avoid

Anti-PatternProblemFix
God ClassOne class does everything — impossible to test or maintainSplit into focused, single-responsibility classes
Singleton overuseHidden global state — breaks unit tests, hides dependenciesPrefer dependency injection
Deep inheritance (>3 levels)Fragile Base Class — base change breaks all descendantsFavor composition; use CRTP or interfaces
Raw ownership pointersLeaks, double-free, dangling — undefined behaviorUse unique_ptr / shared_ptr
Virtual everythingUnnecessary vtable overhead; signals poor designUse final for non-polymorphic classes; prefer CRTP
Premature patternsOver-engineered solution before complexity justifies itStart simple; refactor toward patterns when needed
Mutable global stateAction-at-a-distance bugs, non-deterministic testsEncapsulate in objects; inject where needed

FAQ / Interview Questions

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.
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.
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.
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.
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.
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.

Related C++ Topics on CoodeVerse

Advanced OOP Templates & Generic Programming Smart Pointers Constructors & Destructors Deployment & Best Practices Debugging & Optimization Multithreading 📚 Full C++ Reading Materials

CoodeVerse Editorial Team

Senior software architects and C++ engineers. All patterns tested with GCC 13 and Clang 16, compiled with -Wall -Wextra -std=c++17.