Behavioral patterns define how objects interact (Observer, Strategy, State)
C++ adds unique idioms: RAII, CRTP, PIMPL
Performance tradeoff: runtime polymorphism costs a vtable lookup; static polymorphism via CRTP eliminates it but requires compile-time strategy selection
✦ Definition~90s read
What is Design Patterns in C++?
Static initialization order fiasco is the silent killer of C++ codebases — it occurs when a global or static object's constructor depends on another static object that hasn't been initialized yet. The C++ standard guarantees initialization order within a single translation unit but leaves it undefined across translation units.
★
Imagine you're building LEGO cities.
This means your carefully crafted design patterns, especially singletons and factories relying on static storage, can crash at startup or produce corrupted state without any obvious cause. The fix isn't just about reordering includes; it requires understanding when to use local statics (which are initialized on first use, thread-safely since C++11) versus global statics, and when to avoid static dependencies altogether.
This article dissects how common design patterns interact with this fiasco. The Factory Method often hides static registries that explode at runtime. The Strategy pattern using virtual dispatch works fine, but its modern CRTP variant can introduce subtle template instantiation order issues.
The Meyers Singleton (local static) is your safest bet for thread-safe lazy initialization, but it still has pitfalls with destruction order. The Observer pattern with weak pointers solves lifetime management but introduces its own initialization race if observers register during static init.
The Builder with fluent interfaces is generally safe, but its internal state must not depend on global statics.
Real-world examples: LLVM's PassManager suffered from static init order bugs for years; Chromium's base library explicitly bans non-trivial global statics and uses lazy singletons everywhere. The rule of thumb: if you're writing a design pattern that uses a global or static variable, wrap it in a function-local static or use a dependency injection framework.
Otherwise, you're writing a time bomb that detonates when someone adds a new translation unit.
Plain-English First
Imagine you're building LEGO cities. Instead of figuring out from scratch how to build a fire station every single time, you keep a blueprint — a proven plan that works. Design patterns are those blueprints for software. They're not copy-paste code; they're battle-tested solutions to problems that every developer eventually runs into. Once you know them, you stop reinventing wheels and start speaking a shared language with every other developer on the planet.
Every large C++ codebase — game engines, trading systems, operating systems — is held together not just by algorithms but by structure. That structure comes from design patterns. When a new engineer joins the Unreal Engine team, they don't need to reverse-engineer why the rendering subsystem is built the way it is. The patterns make the intent obvious. That's the superpower patterns give you: code that communicates its own design decisions.
The problem patterns solve is accidental complexity — the kind that grows when you solve the same structural problem a dozen different ways across the same codebase. Without patterns, you end up with object creation scattered everywhere, tight coupling between subsystems that should never talk directly, and notification chains that are impossible to trace. Patterns give you a vocabulary and a discipline: a shared contract between you and every developer who reads your code in the future.
By the end of this article, you'll be able to implement the Gang of Four's most important patterns in modern C++ (C++17/20), understand the performance tradeoffs each one carries, recognise when a pattern is being abused, and walk into a senior engineering interview and explain not just how a pattern works but why it exists and when you'd reject it.
Why C++ Design Patterns Are a Minefield Without Static Initialization Order Awareness
Design patterns in C++ are reusable solutions to common software design problems, but they collide hard with the language's static initialization order fiasco. The core mechanic: when a pattern (e.g., Singleton, Factory) relies on a static or global object, its constructor runs before main() — and the order across translation units is undefined. This means one pattern's static dependency may access another's uninitialized memory, producing silent corruption or crashes.
In practice, this breaks patterns that assume deterministic startup. A Meyers Singleton (local static) is safe because the standard guarantees initialization on first use (C++11+). But a classic Singleton with a file-scope static pointer? That's a race with the linker. The key property: any pattern using non-local static objects across translation units is vulnerable. The fix is to defer initialization or use construct-on-first-use idioms.
Use this knowledge whenever your system has global state — logging, config registries, plugin loaders. In production, a factory pattern that registers types at static init time can fail silently if another translation unit's static map is still zero-initialized. The rule: never let a design pattern's static object depend on another static object's constructor having run. If you do, your 'pattern' is a time bomb.
⚠ Static Init Fiasco Is Not a Bug — It's a Language Feature
The order of static initialization across translation units is undefined by design; relying on it is a design error, not a compiler defect.
📊 Production Insight
A logging singleton initialized at static init time crashed when a factory pattern's static registry tried to log during its own construction — the logger's buffer was still zeroed.
Symptom: intermittent segfaults at program start, reproducible only with specific link orders or compiler optimizations.
Rule: never call a static object's methods from another static object's constructor; use a local-static accessor or explicit init function.
🎯 Key Takeaway
Non-local static objects across translation units have undefined initialization order — design patterns that rely on them are fragile.
Use local-static (Meyers) singletons or explicit initialization functions to guarantee order.
Test startup sequences under different link orders and compiler flags to catch static init fiasco early.
thecodeforge.io
Design Patterns Cpp
Creational Patterns: The Factory Method
In large-scale C++ applications, hardcoding object creation using the new keyword creates tight coupling. If you decide to change the underlying class implementation, you have to hunt down every instance of new across your project. The Factory Method pattern provides an interface for creating objects in a superclass but allows subclasses to alter the type of objects that will be created.
In modern C++, we often pair this with std::unique_ptr to ensure exception safety and automatic memory management, adhering to RAII principles.
But here's the thing: a factory switch-based on an enum is a code smell in itself. It violates the Open/Closed principle because every time you add a new product, you must modify the factory switch. Better alternatives include a registry of factory functions keyed by string (or type index) that can be extended without modifying the factory class. In HFT systems, that table is often populated at startup via static initialisation of plugin modules.
Always declare your base class destructor as virtual. Without it, deleting a derived object via a base pointer causes undefined behavior, often leaking memory because the derived destructor never runs.
📊 Production Insight
Factory Method hides concrete types behind an interface.
Without it, adding a new product type means hunting every new expression across the codebase.
Rule: if you see switch/if-else on type enums, that's a factory waiting to be extracted.
🎯 Key Takeaway
Decouple creation from use.
Factory Method lets you extend a system without modifying existing creation logic.
Never expose constructors directly when the caller doesn't need the concrete type.
Structural Patterns: The Strategy Pattern & Modern CRTP
While traditional Strategy patterns rely on virtual function calls (runtime polymorphism), C++ allows for 'Static Polymorphism' via the Curiously Recurring Template Pattern (CRTP). This avoids the overhead of a VTable lookup, making it a favorite in high-frequency trading (HFT) and game physics engines.
By passing the derived class as a template parameter to the base class, the compiler can resolve function calls at compile-time, allowing for aggressive inlining.
But static polymorphism locks your strategy at compile time. If you need to swap strategies based on a runtime config file or user input, CRTP won't work — you'll need the classic Strategy pattern with virtual functions. The decision tree is simple: if the strategy is known when you write the code (e.g., different validators for different input types that never change), use CRTP. If the strategy is chosen during runtime (e.g., a compression algorithm selected from a UI dropdown), use virtual dispatch. Mix both in a hybrid: use a small virtual interface over a CRTP implementation to get inlining where possible and runtime selection where needed.
Use CRTP when the 'strategy' is known at compile-time. Use the classic Strategy pattern (virtual functions) if you need to swap behaviors dynamically at runtime.
📊 Production Insight
CRTP eliminates vtable overhead but couples the base to the derived at compile time.
You cannot swap strategy at runtime; it's locked into the type.
Rule: use CRTP when the algorithm is fixed at compile time; use virtual functions when you need runtime flexibility.
🎯 Key Takeaway
CRTP enables static polymorphism without virtual dispatch cost.
But it prevents runtime reconfiguration.
Choose your polymorphism strategy based on whether the decision changes at runtime.
CRTP vs Virtual Dispatch Decision Tree
IfStrategy known at compile time (e.g., type-dependent validation)
→
UseUse CRTP for zero overhead and potential inlining.
IfStrategy chosen at runtime (e.g., crypto algorithm selected by config)
The Singleton pattern ensures a class has only one instance and provides a global point of access to it. In C++, the most robust implementation is the 'Meyers Singleton' (named after Scott Meyers), which uses a function-local static variable. Since C++11, the initialization of function-local static variables is guaranteed to be thread-safe by the standard — no mutexes or double-checked locking needed.
But Singleton is often overused. It introduces global state, making code tightly coupled and hard to test. Before applying it, ask: does this truly need to be a single instance? Often a dependency injection framework or a factory that creates one instance (and passes it around) is cleaner. For truly unique resources (hardware driver, logging system, global configuration), Singleton is acceptable, but always pair it with a unit-testing seam: allow replacing the instance via a setter in testing builds.
Be aware of the static initialization order fiasco when two Singletons depend on each other (see production incident above). Meyers Singleton is lazy and thread-safe, but if one Singleton's constructor calls another Singleton, you can still get into trouble. Break cyclical dependencies by using explicit initialization steps in main().
Singleton creates a global point of access that is visible across the entire codebase. This makes mocking in unit tests difficult. Consider using dependency injection or a factory instead. If you must use Singleton, provide a way to replace the instance for testing (e.g., #ifdef TESTING).
📊 Production Insight
Singleton introduces global state visible across threads.
Without careful initialization, you get static initialization order fiasco.
Rule: prefer dependency injection; use Singleton only for genuinely unique system resources.
🎯 Key Takeaway
Singleton is a pattern, not an anti-pattern.
Misused as a global variable bucket, it breaks testability.
When used for a single resource like a log file or hardware interface, it's appropriate.
Behavioral Patterns: The Observer Pattern (with Weak Pointers)
The Observer pattern defines a one-to-many dependency between objects: when one object changes state, all its dependents are notified automatically. In C++, implementing a thread-safe Observer requires careful resource management.
The classic pitfall: storing raw pointers to observers and not notifying them after they're destroyed — causes dangling pointer crashes. The fix: store std::weak_ptr<Observer> and lock before calling. Also protect the subscription list with a std::mutex because notification can happen from any thread.
Another consideration: the order of notification is undefined — never rely on it. And the notifier should not hold locks while calling observers (to avoid deadlocks). A common pattern is to copy the observer list under a lock, then iterate the copy outside the lock.
Using std::weak_ptr in the observer list prevents dangling pointer crashes. The lock() method returns a shared_ptr if the object still exists, or nullptr if it's been destroyed. Always check the result before using.
📊 Production Insight
Observer without weak_ptr leaks listeners.
A destroyed observer that's not removed will cause crashes on notify.
Rule: always store observers as weak_ptr, lock before use, prune expired ones.
🎯 Key Takeaway
Observer decouples subject from observers but introduces registration management.
Thread safety requires mutex around subscription and notification.
Weak_ptr prevents dangling pointer crashes.
Creational Patterns: The Builder (Fluent Interface)
When a class requires many optional or interdependent parameters, constructor overloading becomes unmanageable. The Builder pattern separates the construction of a complex object from its representation, allowing the same construction process to create different representations.
In C++, a fluent Builder returns *this from setter methods, enabling method chaining. This is especially useful for configuration objects, database connections, or HTTP request builders. C++20 introduced designated initializers which can partially replace Builder for simple cases, but for validation and interdependent parameters, Builder is still the right tool.
One production gotcha: Builders often perform validation in their build() method. If validation fails, they can return an std::optional<T> or throw. Never leave the object in a partially constructed state. Another trap: Builders that own raw pointers instead of smart pointers — always store the final object in a std::unique_ptr or return by value if the object is movable.
Think of Builder as a recipe that you assemble step by step, then bake the cake once.
Each setter method adds one ingredient to the recipe.
The build() method is the oven — it validates, cooks, and serves the final object.
Until build() is called, the object does not exist — no partially baked objects.
Fluent chaining makes the code read like a sentence: 'Configure host, then port, then SSL, then build'.
📊 Production Insight
Builder pattern forces construction to be explicit and validated before object creation.
Without Builder, constructors with 10+ parameters become error-prone.
Rule: if a constructor has more than 3 parameters, consider Builder or named parameters via designated initializers.
🎯 Key Takeaway
Builder separates construction from representation.
It produces immutable objects after building.
Use when an object requires many optional interdependent parameters.
Structural Patterns: The Adapter (And Why Wrapping Legacy DLLs Will Haunt You)
The Adapter pattern is how you make a square peg fit a round hole without rewriting the peg factory. You wrap an interface your code expects around an interface some legacy system actually exposes. Sounds clean. Until the legacy system is a C-style DLL with raw pointers, manual memory management, and zero exception safety.
Every call through the adapter becomes a potential crash site. The legacy code throws nothing. Your modern C++ code expects RAII and exceptions. So you wrap each call in try-catch blocks that translate error codes into exceptions, pray you don't leak when a callback fires mid-translation, and write integration tests that mock the DLL because you can't run it on your dev machine.
The real problem is not the adapter itself — it's assuming adapters are a one-line static_cast. They're not. Each adapter is a contract negotiation between two error handling philosophies. Document the failure modes. Assert preconditions at the boundary. And never, ever let a legacy pointer escape into modern code without an ownership wrapper.
No output — compiles, but the adapter swallows 3 failure paths you'll debug at 3 AM.
⚠ Production Trap:
Never let a C-style callback call into modern C++ code without locking. Legacy APIs often fire callbacks from threads you don't control. Wrap the callback in a mutex or queue the event. One dangling callback = one corrupted heap.
🎯 Key Takeaway
Every adapter is a failure boundary — document the error translation, own the ownership transfer, and test the crash paths.
Creational Patterns: Abstract Factory (The Mother Of All Constructors)
The Abstract Factory is what you reach for when your code needs to create families of related objects — think UI widgets that must match an OS theme, or game objects that need to be consistent within a level. One factory for Windows buttons and scrollbars, another for macOS. The caller never touches a concrete class.
In C++, the classic implementation is a virtual base factory with a virtual destructor, then concrete factories that new up objects. Works. But here's the C++ twist: if your factory returns std::unique_ptr or raw pointers, you've committed to heap allocation even when stack allocation would be faster. For embedded or latency-sensitive paths, that's a non-starter.
Modern C++ fix: make the factory a template policy parameter. The caller decides allocation strategy at compile time. The abstract factory becomes a concept, not a base class. You lose runtime polymorphism but gain zero-cost abstraction and the ability to allocate from a pool. For most server code? The virtual factory is fine. For a game engine? The virtual dispatch alone will cost you a cache miss per frame.
The real insight: ask yourself if you need runtime variation at all. If you compile per platform, an Abstract Factory is overkill. #ifdef the concrete types. If you need runtime switching (e.g. a config file changes the theme), use the factory. Know the cost of your abstraction before you abstract.
Compiles. OrcSword and OrcShield are hidden. Client doesn't know Orcs exist. That's the point.
🔥Senior Shortcut:
If your factory only ever returns one concrete type at runtime, remove it. Abstract Factory is for families, not single objects. A single-object factory is just a virtual constructor — and you probably don't need that either.
🎯 Key Takeaway
Abstract Factory buys you runtime family consistency at the cost of heap allocation and virtual dispatch. Measure before you use it, and template it when the family is known at compile time.
Structural Patterns: The Bridge (Pimpl Is Not Just For Hiding Headers)
The Bridge pattern decouples an abstraction from its implementation so they can vary independently. In C++, that's exactly what the Pointer-to-Implementation (Pimpl) idiom does. But Pimpl is usually sold as a compilation firewall — hide the private members, reduce rebuild times. The Bridge is deeper: it lets you swap implementations at runtime without touching the abstraction's interface.
Real example: a cross-platform graphics API. Your Renderer abstraction holds a unique_ptr<RendererImpl>. On Windows, RendererImpl calls DirectX. On Linux, it calls Vulkan. The client code never includes a single platform header. Write the abstraction once. Swap the backend per platform or even per config.
The C++ trap: people make the Bridge too fat. Every method on the abstraction adds a virtual call through the impl pointer. For a renderer with DrawTriangle, SetTexture, ClearScreen — fine. For a renderer with 200 methods? You've created a slow fat interface that changes every week. Keep the bridge thin. Five to ten methods max. If your impl needs 200, your abstraction is wrong — break it into multiple bridges (e.g. TextureManager, ShaderCompiler).
Second trap: the Bridge tempts you to pass non-trivial state through the impl pointer every call. Cache state in the abstraction layer. The impl should be stateless or nearly stateless. Otherwise you're just doing slow delegation with extra steps.
Renderer.h compiles. No platform includes leak. Impl can be swapped at link time or runtime.
💡Production Trap:
Don't put std::unique_ptr to a forward-declared type in a header unless you declare the destructor in the .cpp file. The compiler needs the complete type to destroy the unique_ptr. This is the most common Bridge/Pimpl compile error. Always declare ~Renderer() in the header and define it in the .cpp after the impl definition.
🎯 Key Takeaway
Bridge = Pimpl with a purpose: decouple abstraction from implementation so both can evolve independently. Keep the bridge thin, the impl stateless, and the destructor in the .cpp file.
The Catalog of C++ Examples
A design pattern catalog transforms abstract theory into executable proof. For C++ developers, each example must expose the language-specific pitfalls that differentiate a working pattern from a silently broken one. Start with Creational patterns: the Factory Method reveals how virtual constructors interact with static storage duration; the Builder demonstrates why move semantics are non-negotiable for fluent interfaces; and the Abstract Factory exposes the constructor explosion when template parameters replace runtime dispatch. Structural patterns demand attention to memory layout: the Adapter wrapping a legacy DLL forces explicit lifetime management via RAII, while the Bridge (Pimpl) hides compilation dependencies but risks double indirection overhead. Behavioral patterns test ownership semantics: the Observer with weak pointers eliminates cyclic references, and the Strategy via CRTP avoids virtual table costs at the expense of compile-time coupling. Each example in the catalog should compile under C++17 or later, include a single main() demonstrating the pattern's invariant, and embed a static_assert or assertion to validate correctness. The goal is not exhaustive coverage but canonical implementations that survive code review and production hardening.
A catalog without lifetime rules is a memory leak factory. Always document how each pattern manages ownership — raw pointers, shared_ptr, or weak_ptr — before merging into a codebase.
🎯 Key Takeaway
Catalog every pattern with a runnable, self-verifying example that exposes C++-specific failure modes like dangling references or static initialization order.
Conclusion
Design patterns in C++ are not abstract ideals — they are survival tools against the language's sharp edges. This series exposed the hidden costs: static initialization order in Factory Methods, virtual table bloat in classic Strategy, and reference cycles in Observer. The modern C++ answer is not to abandon patterns but to reforge them with RAII, templates, and the Standard Library. Move semantics transform Builder from a verb into a zero-cost abstraction; CRTP replaces runtime polymorphism in Strategy without heap allocation; and Meyers Singleton eliminates thread-safety worries via local static initialization. Yet patterns remain traps without rigorous ownership semantics — every shared_ptr in an Abstract Factory is a decision, every weak_ptr in an Observer is a promise broken safely. The final lesson: never cargo-cult a pattern from Java or Go. C++ demands that you trace the destructor path before writing the constructor. As you build your own catalog of patterns, write the failure case first: the dangling pointer, the static initialization bomb, the infinite callback recursion. Only then does the pattern earn its place in production code. The code below encapsulates this philosophy: a self-verifying observer that proves ownership correctness at compile time.
Never declare a virtual destructor without tracing all subclass lifetimes. A single missing override in a 10k-line codebase will leak silently for years.
🎯 Key Takeaway
Every C++ pattern must validate ownership and destruction semantics as part of its design, not as an afterthought.
thecodeforge.io
Design Patterns Cpp
Strategy Pattern Context Object — Stop Mixing Algorithm Choice With Execution State
Most C++ Strategy Pattern tutorials glom the algorithm selector right into the context, so your context is both deciding which strategy to use AND holding data. That's a mess when you want unit tests, dynamic swapping, or serialization. The fix: a dedicated Context Object that stores state (input, output, config) independently from the Strategy selection logic. The context becomes a dumb holder — no if-else, no strategy instantiation. You pass a strategy reference into its methods, or inject via setter. Now you can reuse the same context across different strategies without reallocating. The strategy selection logic lives in a separate factory or in the caller, decoupling choice from execution. Your code survives changing requirements because state isn't tangled with decision making. Plus, you can mock strategies in tests without touching the context's internal guts. This pattern is especially brutal when your state includes resource handles — don't let strategy choice own cleanup.
State and algorithm selection are separate — test strategies independently from context.
⚠ Production Trap:
If your Context owns the Strategy pointer, you've re-created the original coupling. The context must be stateless regarding the strategy — pass it in, don't hold it.
🎯 Key Takeaway
Strategy pattern: context holds state, not the algorithm choice.
● Production incidentPOST-MORTEMseverity: high
Static Initialization Order Fiasco Brings Down Trading Platform
Symptom
Production server crashes immediately on startup after deploying a small config change. No stack trace points to the new code — the crash is in a function that reads a configuration value from a Singleton. The same binary works fine in staging.
Assumption
The Singleton using the Meyers' singleton is thread-safe and lazy-initialized, so it should be available when needed. The team assumed static initialization order was deterministic across compilation units.
Root cause
The config manager Singleton used a local static variable (Meyers) which is lazy and thread-safe. But another Singleton (initially not depending on config) was modified during the hotfix to call ConfigManager::instance().get() in its constructor. Since both Singletons were initialized via local statics, the dependency was hidden — the compilers did not guarantee that ConfigManager was initialized before the dependent Singleton because the call happened inside the dependent Singleton's initialization, which is also a local static. This is the static initialization order fiasco: the order of initialization of local statics is well-defined for a single thread, but when one local static's constructor triggers another, you get a circular dependency that manifests as access to uninitialized memory.
Fix
Changed the config manager to use a simple extern global variable initialized at program start (before main), and enforced initialization order via a dedicated initialization function called at the top of main(). Alternatively, moved the config dependency out of the Singleton's constructor into a separate initialization method called after all Singletons are guaranteed to be alive.
Key lesson
Meyers Singleton is thread-safe but does not solve initialization order across interdependent Singletons.
Never allow one Singleton's constructor to call another Singleton's instance — that creates a hidden dependency graph you cannot control.
Use explicit initialization steps in main() for resources with cross-Singleton dependencies, or restructure to avoid Singleton interdependence entirely.
Production debug guideCommon symptoms and actions for pattern-related production bugs5 entries
Symptom · 01
Object does not behave polymorphically — slicing occurs when passing by value
→
Fix
Check if base destructor is virtual. If not, add virtual ~Base() = default; in the base class. Then ensure all derived destructors are overridden (use override keyword).
Symptom · 02
Observer notification crashes with dangling pointer or iterator invalidation
→
Fix
Check if observers are stored as raw pointers. Replace with std::weak_ptr and lock before calling. Also ensure the observer list is not modified during iteration (use a copy or lock with mutex).
Symptom · 03
Singleton returns a different instance across threads (race condition)
→
Fix
Replace raw pointer initialization with Meyers Singleton: static T& instance() { static T inst; return inst; }. This is thread-safe in C++11 and later.
Symptom · 04
Factory returns null pointer but code expects a valid object
→
Fix
Check if the factory switch/default case returns nullptr for unknown types. Replace with std::optional or throw a well-defined exception. Alternatively, use a map of string to factory function.
Symptom · 05
CRTP base class method calls wrong derived implementation — linker error 'undefined reference'
→
Fix
Make sure the derived class defines the method that the base calls via static_cast<Derived*>(this)->foo();. The method must be defined inline in the derived class header (template code cannot be in a separate .cpp unless explicitly instantiated).
★ Quick Debug Cheat Sheet — C++ Design Pattern Implementation IssuesUse these commands to diagnose pattern-related problems fast.
VTable not visible / Polymorphism not working−
Immediate action
Check if base destructor is virtual and if the derived class has `override` on all virtual functions.
Commands
nm -C ./a.out | grep vtable
objdump -t ./a.out | grep .vtable
Fix now
Add virtual ~Base() = default; in base class. Rebuild with -O0 -g to see vtable symbols.
Observer: crash when notifying after listener destroyed+
Immediate action
Check observer storage: are they raw pointers? If so, switch to `std::weak_ptr`.
Design patterns are templates for solutions, not mandatory boilerplate. Use them only when the structural problem justifies the abstraction.
2
Modern C++ (17/20) favors value semantics and smart pointers over raw pointers when implementing classic GoF patterns.
3
Static polymorphism (CRTP) is a unique C++ tool that provides the benefits of the Strategy pattern without the runtime cost of virtual dispatch.
4
Patterns facilitate a 'Ubiquitous Language' among developers, making high-level design reviews significantly more efficient.
5
The Meyers Singleton is the safest Singleton in modern C++
use it, but only when you genuinely need a single instance.
6
Observer must use weak_ptr to avoid dangling listeners; always protect the observer list with a mutex if notifications can come from multiple threads.
Common mistakes to avoid
5 patterns
×
Over-engineering with Abstract Factory where a simple function suffices
Symptom
Codebase has a class hierarchy for creating objects that have only two variants, maintained across multiple files with virtual methods, making change harder than direct instantiation.
Fix
Replace with a single factory function (or a lambda) that returns a std::unique_ptr to the product. Expand to a class only when you need multiple families or extension points.
×
Using Singleton as a global variable repository for the entire application
Symptom
Unit tests fail because Singletons hold state from previous tests; modules are tightly coupled to the Singleton, making them impossible to test in isolation.
Fix
Refactor to dependency injection: pass dependencies as constructor parameters. Use a factory that creates one instance but allows replacement during testing via a setter or an interface.
×
Forgetting virtual destructor in base class of a pattern hierarchy
Symptom
Memory leaks or undefined behavior when deleting derived object via base pointer; tools like Valgrind report 'definitely lost' memory.
Fix
Always declare virtual ~Base() = default; in the base class. If using an interface class, provide a virtual destructor with an empty body.
×
Observer pattern with raw pointers and no cleanup on observer destruction
Symptom
Random crashes when subject notifies after some observers have been destroyed; debugger shows access to freed memory.
Fix
Use std::weak_ptr in the subject's list of observers. Before calling update(), lock the weak pointer; if it's expired, remove it from the list.
×
Trying to use CRTP for runtime strategy selection
Symptom
Compilation error or template instantiation explosion when trying to pass a CRTP type selected at runtime.
Fix
CRTP is compile-time polymorphism. For runtime strategy selection, use virtual functions or a combination: a small virtual interface that wraps CRTP implementations.
INTERVIEW PREP · PRACTICE MODE
Interview Questions on This Topic
Q01SENIOR
Explain the 'Open-Closed Principle' and show how the Strategy pattern he...
Q02SENIOR
How would you implement a thread-safe Singleton in C++11? What is 'Doubl...
Q03SENIOR
Compare Runtime Polymorphism (Virtual Functions) and Static Polymorphism...
Q04SENIOR
Describe the 'Object Pool' pattern. In what scenarios (e.g., Game Develo...
Q05SENIOR
What is the 'Visitor' pattern, and how does it allow you to add new oper...
Q01 of 05SENIOR
Explain the 'Open-Closed Principle' and show how the Strategy pattern helps a class adhere to it.
ANSWER
The Open-Closed Principle states that software entities should be open for extension but closed for modification. The Strategy pattern achieves this by defining a family of algorithms in separate classes that implement a common interface. The context class holds a reference to the strategy interface and delegates behaviour to it. To add a new strategy, you write a new class that implements the interface — no modification to the context or existing strategies is needed. In C++, this is often implemented with std::function or a pure virtual interface Strategy.
Q02 of 05SENIOR
How would you implement a thread-safe Singleton in C++11? What is 'Double-Checked Locking' and why is it usually unnecessary in modern C++?
ANSWER
In C++11 and later, the Meyers Singleton is thread-safe by the standard: static T& instance() { static T inst; return inst; }. The compiler ensures that the local static variable is initialised exactly once, even in multi-threaded code. Double-checked locking was used prior to C++11 to avoid locking on every access after the Singleton was already initialised. It involved: 1) check if instance is null without lock; 2) if null, lock; 3) check again; 4) create instance. It's error-prone due to memory ordering issues. Since C++11, the language guarantees safe initialisation, so double-checked locking is unnecessary (and still risky if implemented manually).
Q03 of 05SENIOR
Compare Runtime Polymorphism (Virtual Functions) and Static Polymorphism (CRTP). What are the memory and performance implications of each?
ANSWER
Runtime polymorphism uses vtables: each polymorphic class has a vtable pointer (typically 8 bytes per object on 64-bit). Each virtual call requires an indirect jump through the vtable, which can prevent inlining and cause branch misprediction. CRTP achieves compile-time polymorphism: no vtable, no indirect jumps, the function is resolved at compile time and can be inlined. However, CRTP locks the type at compile time — you cannot have a container of different CRTP-derived types (unless you use type erasure). Memory overhead: vtable pointer per object vs. zero overhead per object with CRTP (only the base template is empty). Performance: in latency-critical paths (HFT, game physics), CRTP can be 5–20% faster due to inlining and no vtable.
Q04 of 05SENIOR
Describe the 'Object Pool' pattern. In what scenarios (e.g., Game Development) would you use this instead of standard `new/delete` allocation?
ANSWER
Object Pool is a creational pattern that pre-allocates a fixed number of objects and reuses them instead of allocating and deallocating individually. In game development, objects like bullets, particles, or enemies are created and destroyed frequently. Using new/delete leads to memory fragmentation and allocation latency spikes. An object pool maintains a fixed-size array or a free list. When an object is needed, it is taken from the pool (fast, O(1)), and when it's no longer needed, it's returned to the pool (not freed). This eliminates memory fragmentation and allocation overhead. In C++, the pool can be implemented using a union with an index to the next free node stored inside each pooled object, or using a std::vector of std::optional. It is also useful in embedded systems with limited heap.
Q05 of 05SENIOR
What is the 'Visitor' pattern, and how does it allow you to add new operations to existing class hierarchies without modifying them? What is its main drawback?
ANSWER
The Visitor pattern lets you define a new operation on a set of objects without changing their classes. It works by having a 'visitor' interface with a visit method for each concrete class in the hierarchy. Each class accepts a visitor by calling visitor.visit(this). To add a new operation, you create a new class implementing the visitor interface — no changes needed to the existing classes. The main drawback is that adding a new concrete class to the hierarchy requires updating every existing visitor with a new visit() method (violating the Open-Closed principle). In C++, Visitor is often implemented with std::variant and std::visit as an alternative (pattern matching).
01
Explain the 'Open-Closed Principle' and show how the Strategy pattern helps a class adhere to it.
SENIOR
02
How would you implement a thread-safe Singleton in C++11? What is 'Double-Checked Locking' and why is it usually unnecessary in modern C++?
SENIOR
03
Compare Runtime Polymorphism (Virtual Functions) and Static Polymorphism (CRTP). What are the memory and performance implications of each?
SENIOR
04
Describe the 'Object Pool' pattern. In what scenarios (e.g., Game Development) would you use this instead of standard `new/delete` allocation?
SENIOR
05
What is the 'Visitor' pattern, and how does it allow you to add new operations to existing class hierarchies without modifying them? What is its main drawback?
SENIOR
FAQ · 5 QUESTIONS
Frequently Asked Questions
01
Is the Singleton pattern an anti-pattern in C++?
It is often called an anti-pattern because it introduces global state, making code brittle and hard to test. However, for genuinely unique resources (like a hardware driver interface or a unique log file), it is valid. In C++11 and later, the 'Meyers Singleton' (using a static local variable) is the thread-safe standard implementation.
Was this helpful?
02
What is the PIMPL idiom and why is it a structural pattern?
PIMPL (Pointer to IMPLementation) is a structural pattern used to hide a class's private members in a separate implementation class. This reduces compilation times (by breaking header dependencies) and provides a stable ABI (Application Binary Interface).
Was this helpful?
03
Should I use std::shared_ptr or std::unique_ptr for patterns?
Prefer std::unique_ptr by default for patterns like Factory or Builder. It represents clear ownership. Only move to std::shared_ptr if you have multiple objects that truly need to share ownership of the same resource, such as in a complex Observer notification graph.
Was this helpful?
04
How do I implement a thread-safe Observer in C++?
A thread-safe Observer requires a std::mutex to protect the list of subscribers. Furthermore, you should use std::weak_ptr to store observers. This prevents 'lapsed listener' memory leaks—before notifying, you attempt to lock the weak pointer; if it's expired, the observer has been destroyed and you can safely remove it from your list.
Was this helpful?
05
What is the difference between the Strategy pattern and the State pattern?
Both use composition to change behaviour. The key difference: in Strategy, the client sets the algorithm, and it stays that way until changed. In State, the object changes its behaviour automatically based on internal state transitions — the state context knows the allowed transitions. Strategy is about choosing an algorithm; State is about managing state-dependent behaviour.