Skip to content

Design Patterns in C++

Beginning of the lesson

Design Patterns in C++

How classic patterns dissolve, sharpen, or change shape in modern C++

Big Idea: The Gang-of-Four patterns were written for a language without RAII, templates, lambdas, or move semantics. In modern C++ (C++17/20) many of those patterns are reshaped by the language itself: manual cleanup becomes RAII, runtime virtual dispatch is often replaced by compile-time CRTP, header dependencies are cut by the pimpl idiom, and Strategy and Observer shrink to a std::function and a lambda. A pattern is a solution shape, not a class hierarchy you must copy verbatim.
By the end you will be able to:
  • turn any resource into an RAII wrapper and write a generic scope guard;
  • use the CRTP for zero-overhead static polymorphism and mixins;
  • apply the pimpl idiom to build a compilation firewall and a stable ABI;
  • implement Strategy and Observer with std::function, lambdas, and weak_ptr;
  • weigh the honest trade-offs of each choice (overhead, coupling, flexibility).

1. RAII as a pattern generator (scope guard)

You already use RAII for memory (unique_ptr), locks (std::lock_guard) and files (std::fstream). RAII is not one pattern; it is a factory of patterns: any resource with an acquire and a release step can be wrapped so that release happens automatically in the destructor, on every path, including exception unwinding.

When there is no ready-made wrapper, the scope guard generalises RAII to run arbitrary cleanup at end of scope. It stores a callable and invokes it in its destructor, unless you dismiss() it first (for the success path of a commit/rollback):

template <class F>
class ScopeGuard {
    F cleanup_;
    bool active_ = true;
public:
    explicit ScopeGuard(F f) : cleanup_(std::move(f)) {}
    void dismiss() noexcept { active_ = false; }
    ~ScopeGuard() { if (active_) cleanup_(); }   // runs on every exit path

    ScopeGuard(const ScopeGuard&) = delete;       // non-copyable
    ScopeGuard& operator=(const ScopeGuard&) = delete;
};

template <class F>
ScopeGuard<F> makeGuard(F f) { return ScopeGuard<F>(std::move(f)); }

Usage reads like a manual finally, but is exception-safe and needs no try/catch:

FILE* f = std::fopen("data.bin", "rb");
auto guard = makeGuard([&]{ if (f) std::fclose(f); });   // always closes
// ... use f; if anything throws here, guard still closes the file ...

C++20 / library note: the same idea is standardised in the Library Fundamentals TS as std::experimental::scope_exit, and appears in the GSL as gsl::finally. Prefer those over rolling your own when available.

Honest caveat: a destructor that throws during stack unwinding calls std::terminate. Cleanup lambdas should be noexcept in spirit - swallow or log errors, never let them escape a destructor.

Continue this lesson on YourAIZone

The full lesson has the complete explanation, worked examples, common mistakes, tests with instant checking and an AI tutor. Start right away as a guest, or create an account to keep your progress.

In the full lesson

  • The complete explanation
  • Worked examples
  • Common mistakes and how to avoid them
  • Test questions: 15
  • AI tutor that answers questions about the topic
  • Progress tracking for students, parents and teachers