The Decorator pattern adds optional behavior to an object by wrapping it in another object that implements the same interface. In modern C++, that lets you combine concerns such as logging, caching, or authorization at runtime without creating a separate subclass for every combination.
How the Decorator pattern works
A component defines the operations clients need. A concrete component supplies the baseline behavior, while each decorator also implements the component interface and holds another component. A decorator can forward an operation unchanged, add work before or after forwarding, or both. Because every layer presents the same interface, client code can use the finished chain as if it were the original component.
Layers can be stacked in different orders. For example, a client could wrap a service first with caching and then with logging, or reverse the order. The order matters: the outer layer receives the call first, and each layer decides what happens before delegating inward.
A minimal modern C++ implementation
This example uses std::unique_ptr to make ownership of the wrapped object explicit. The decorators own the next layer in the chain.
#1 Best Overall
#include <memory>
#include <utility>
struct Component {
virtual ~Component() = default;
virtual void operation() = 0;
};
struct ConcreteComponent final : Component {
void operation() override {
// Baseline work
}
};
struct Decorator : Component {
explicit Decorator(std::unique_ptr<Component> inner)
: inner_(std::move(inner)) {}
void operation() override {
inner_->operation();
}
protected:
std::unique_ptr<Component> inner_;
};
struct Logging final : Decorator {
using Decorator::Decorator;
void operation() override {
// Log before
Decorator::operation();
// Log after
}
};
To construct a chain, move ownership from one layer into the next:
std::unique_ptr<Component> component =
std::make_unique<Logging>(
std::make_unique<ConcreteComponent>());
component->operation();
The base interface has a virtual destructor so deleting a derived object through a Component pointer is safe. The sample keeps the interface narrow and uses RAII: when the owning pointer leaves scope, the complete chain is destroyed. Use shared ownership only when multiple independent owners actually need to share a component.
When Decorator is a good fit
- Behavior is optional or selected at runtime. A factory or client can choose which concerns to add for a particular object.
- Subclass combinations would proliferate. Instead of creating types for every combination of logging, caching, retries, and other features, compose the needed layers.
- The original type is difficult to extend. A wrapper can add behavior without changing the concrete component, provided it can be used through the shared interface.
Common applications include stream and I/O layers, middleware, instrumentation, policy enforcement, caching, and serialization pipelines. Stream layers are a familiar C++ use of the pattern; see the C++ Decorator example.
Decorator versus inheritance and Adapter
Inheritance defines behavior in types ahead of time; Decorator composes behavior around an object. Adapter addresses a different need: it translates one interface into another so otherwise incompatible types can work together. Decorator preserves the component interface, while Adapter changes the interface presented to its client. The C++ pattern catalog lists Adapter and Decorator separately as structural patterns.
Recommended Free Tools
| Choice | Use it when | Key consequence |
|---|---|---|
| Inheritance | A subtype relationship is appropriate and behavior is stable across instances. | Behavior is tied to the class hierarchy; many feature combinations can lead to many subclasses. |
| Decorator | You need optional behavior combinations around objects while keeping their interface. | Composition is flexible, but runtime wrapper order and traversal matter. |
| Adapter | A client and an existing object have incompatible interfaces. | The adapter translates between interfaces rather than simply preserving the component interface. |
Trade-offs to consider
- Runtime overhead: polymorphic calls, wrapper traversal, and allocations can matter in hot paths. Measure in the workload that matters rather than assuming the cost is significant or negligible.
- Order-sensitive behavior: logging outside a cache may observe different calls than logging inside it. Make composition order explicit in a factory or named helper when it is not obvious.
- Debugging: behavior and failures can originate at any point in a chain. Keep layers focused and give composition code a clear place to inspect.
- Ownership: decide whether a wrapper owns its component or merely refers to it. The owning
unique_ptrexample is a simple default for a single-owner chain; use a different ownership model only when the design requires it.
Applying modern C++ practices
The C++ Core Guidelines frame modern C++ as C++11 and newer, with attention to interfaces, resource and memory management, concurrency, and library design. Their guidance supports several practical choices here: use RAII and smart pointers to express ownership, avoid unnecessary shared ownership, keep polymorphic interfaces small, and document chains whose order affects behavior. See the C++ Core Guidelines.
Decorator is a useful fit when behavior needs to be composed around a stable interface. If you need to reconcile incompatible interfaces instead, look to Adapter; if the chain adds complexity or overhead without a real need for runtime composition, a simpler design may be easier to maintain.
Quick Recap
Best Value
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

