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.
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. 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.
Related articles
- C++ C++ Exceptions vs Error Codes
Compare exceptions and error codes in C++ for handling failures — performance, ergonomics, and when each style fits real codebases.
- C++ C++ std::string_view Explained
Learn how std::string_view gives you cheap, non-owning views into strings — when to use it, how it speeds up code, and how to avoid dangling references.
- C++ C++ Unit Testing with GoogleTest
Set up GoogleTest in a CMake project and write clear unit tests with assertions, fixtures, and parameterized tests for confident C++ code.
- C++ C++ Classes, Constructors, and the Rule of Three
Design C++ classes with constructors, destructors, member initializer lists, and the Rule of Three and Five to manage resources safely.