Skip to content
C Codeloom
C++

C++ Design Patterns Overview

Tour the most useful design patterns in modern C++ — singleton, factory, observer, strategy, RAII — and learn when each one earns its keep.

·4 min read · By Codeloom
Intermediate 9 min read

What you'll learn

  • Recognize core patterns: factory, strategy, observer, singleton
  • Understand how modern C++ features simplify classic patterns
  • Apply RAII as the dominant resource management pattern
  • Choose the right pattern instead of forcing one
  • Avoid over-engineering with pattern soup

Prerequisites

  • Classes and inheritance — see /blog/cpp-classes-and-objects

Design patterns are reusable solutions to recurring problems. In C++, many classic patterns from the Gang of Four book look different than they do in Java or C# because the language gives you templates, value semantics, and RAII. Knowing the patterns helps you name shapes you already see in code.

What and Why

A pattern is a named recipe. Instead of saying “we will plug different sorting algorithms into the report generator at runtime,” you say “strategy pattern” and a teammate immediately knows the shape. Patterns improve communication, guide refactoring, and prevent reinventing wheels. They are not, however, mandatory architecture. A pattern that does not solve a real problem in your code only adds noise.

In C++ specifically, patterns interact strongly with ownership and lifetime. Many GoF examples assume a garbage collector. In C++ you must decide whether participants own each other, share ownership, or merely observe.

Mental Model

Group patterns by what they vary:

  • Creational patterns vary how objects come into existence: factory, builder, singleton.
  • Structural patterns vary how objects compose: adapter, decorator, facade.
  • Behavioral patterns vary how objects interact: strategy, observer, command, visitor.

Then ask whether the variation is needed at compile time or runtime. C++ gives you templates for compile-time variation (zero overhead, errors at build) and virtual functions for runtime variation (flexibility, vtable cost).

Hands-on Example

Here is a strategy pattern that swaps a compression algorithm at runtime.

#include <memory>
#include <string>

struct Compressor {
    virtual ~Compressor() = default;
    virtual std::string compress(const std::string& data) = 0;
};

struct GzipCompressor : Compressor {
    std::string compress(const std::string& data) override {
        return "gzip(" + data + ")";
    }
};

struct ZstdCompressor : Compressor {
    std::string compress(const std::string& data) override {
        return "zstd(" + data + ")";
    }
};

class Archive {
public:
    Archive(std::unique_ptr<Compressor> c) : c_(std::move(c)) {}
    std::string store(const std::string& d) { return c_->compress(d); }
private:
    std::unique_ptr<Compressor> c_;
};
Archive ──holds──> Compressor (interface)
                     ^      ^
                     |      |
                Gzip       Zstd
                (impl)     (impl)

client picks impl, injects, Archive never knows which.
Strategy pattern: Archive delegates to a swappable Compressor.

The Archive does not care which algorithm runs. You can add new compressors without changing Archive. The same shape appears as policy-based design when you template the strategy instead of using virtual calls.

Common Pitfalls

The most common mistake is pattern overuse. A singleton seems convenient until tests need a different instance. A factory class is overkill if a plain function would do. Visitor is powerful but punishing to maintain when types change often.

A second pitfall is ignoring ownership. If your observer pattern stores raw pointers to subjects that may be destroyed, you have written a dangling-pointer factory. Use std::weak_ptr or explicit unregister calls.

A third pitfall is assuming virtual dispatch is free. For hot loops, templates or std::variant plus std::visit often beat virtual functions while keeping the abstraction.

Practical Tips

Start without patterns. Write the straightforward code, and only refactor to a pattern when you feel real friction — duplicated branching on a type, repeated construction logic, or a need to extend behavior without touching existing code.

Prefer RAII over manual cleanup patterns. A std::unique_ptr, std::lock_guard, or custom scope guard removes whole classes of bugs.

Use std::function and lambdas for lightweight strategy and command patterns. You rarely need a class hierarchy when a callable does the job.

For singletons, prefer the Meyers singleton (static T& instance() with a local static) over global pointers. Better still, pass dependencies explicitly so tests can substitute fakes.

Wrap-up

Patterns are vocabulary, not architecture. Learn the common shapes — factory, strategy, observer, decorator, RAII — and you will recognize them in libraries you read. Apply them when they reduce duplication or clarify intent, and resist them when they would only add ceremony. Modern C++ often expresses the same ideas with templates, lambdas, and value types instead of inheritance trees.