The Decorator Pattern in C++: Adding Behavior by Composition (Streams, Logging, Formatters)

Key takeaways

How the Decorator pattern wraps objects to add behavior in C++, why composition beats subclass explosion here, stream and logging examples, common errors, and production patterns.

What is the Decorator Pattern? Why Do We Need It?

Problem Scenario: Feature Combination Explosion

Problem: To add milk, sugar, and whipped cream to coffee, you would need to create a class for every combination.

// Bad example: Class explosion
class Coffee {};
class CoffeeWithMilk : public Coffee {};
class CoffeeWithSugar : public Coffee {};
class CoffeeWithMilkAndSugar : public Coffee {};
class CoffeeWithMilkAndSugarAndWhip : public Coffee {};
// The more combinations, the more classes you need

With three optional add-ons you already need eight classes to cover every combination, and each new add-on doubles the count. Worse, the combinations are fixed at compile time: if a customer’s order arrives as data (a config file, a UI selection), you end up writing a big if ladder that maps flags to the right subclass.

Solution: The Decorator Pattern allows you to dynamically add features. A Decorator wraps a Component to add functionality.

// Good example: Decorator
std::unique_ptr<Coffee> coffee = std::make_unique<SimpleCoffee>();
coffee = std::make_unique<MilkDecorator>(std::move(coffee));
coffee = std::make_unique<SugarDecorator>(std::move(coffee));
// Combine features at runtime

The key idea is that a decorator is a Coffee (it implements the same interface) and also has a Coffee (the object it wraps). Because callers only see the interface, they cannot tell whether they hold a plain coffee or a coffee wrapped three times. Each decorator does its small piece of work and forwards the rest of the call to the inner object. N add-ons now cost N classes instead of 2^N, and the combination is chosen at runtime.

flowchart TD
    component["Component (Coffee)"]
    simple[SimpleCoffee]
    decorator[Decorator]
    milk[MilkDecorator]
    sugar[SugarDecorator]
    
    simple -.->|inherits| component
    decorator -.->|inherits| component
    milk -.->|inherits| decorator
    sugar -.->|inherits| decorator
    decorator --> component

Basic Structure

There are four roles. Coffee is the abstract component interface. SimpleCoffee is the concrete component that does the base work. CoffeeDecorator is an abstract base that stores the wrapped object; it exists only so that every concrete decorator does not have to repeat the member and constructor. MilkDecorator and SugarDecorator are concrete decorators that add their bit and delegate.

#include <iostream>
#include <memory>
#include <string>
class Coffee {
public:
    virtual std::string getDescription() const = 0;
    virtual double cost() const = 0;
    virtual ~Coffee() = default;
};
class SimpleCoffee : public Coffee {
public:
    std::string getDescription() const override {
        return "Simple coffee";
    }
    
    double cost() const override {
        return 2.0;
    }
};
class CoffeeDecorator : public Coffee {
public:
    CoffeeDecorator(std::unique_ptr<Coffee> c)
        : coffee(std::move(c)) {}
    
protected:
    std::unique_ptr<Coffee> coffee;
};
class MilkDecorator : public CoffeeDecorator {
public:
    using CoffeeDecorator::CoffeeDecorator;
    
    std::string getDescription() const override {
        return coffee->getDescription() + " + Milk";
    }
    
    double cost() const override {
        return coffee->cost() + 0.5;
    }
};
class SugarDecorator : public CoffeeDecorator {
public:
    using CoffeeDecorator::CoffeeDecorator;
    
    std::string getDescription() const override {
        return coffee->getDescription() + " + Sugar";
    }
    
    double cost() const override {
        return coffee->cost() + 0.3;
    }
};
int main() {
    std::unique_ptr<Coffee> coffee = std::make_unique<SimpleCoffee>();
    std::cout << coffee->getDescription() << ": $" << coffee->cost() << '\n';
    
    coffee = std::make_unique<MilkDecorator>(std::move(coffee));
    std::cout << coffee->getDescription() << ": $" << coffee->cost() << '\n';
    
    coffee = std::make_unique<SugarDecorator>(std::move(coffee));
    std::cout << coffee->getDescription() << ": $" << coffee->cost() << '\n';
}

Output:

Simple coffee: $2
Simple coffee + Milk: $2.5
Simple coffee + Milk + Sugar: $2.8

Two details in this code are easy to get wrong. First, the variable is declared as std::unique_ptr<Coffee>, not auto. With auto coffee = std::make_unique<SimpleCoffee>(); the type is std::unique_ptr<SimpleCoffee>, and the next line fails to compile because a unique_ptr<MilkDecorator> cannot be assigned to a unique_ptr<SimpleCoffee>. Declaring the interface type is what lets the same variable hold whatever the outermost layer happens to be.

Second, using CoffeeDecorator::CoffeeDecorator; inherits the base constructor so concrete decorators need no boilerplate. The base constructor takes the unique_ptr by value, which means the caller must hand over ownership explicitly with std::move. That is intentional: a decorator owns what it wraps, and destroying the outer layer destroys the whole chain.

cost() shows the forwarding rule most decorators follow: call the wrapped object, then adjust the result. Whether you do your work before or after the forwarded call is a real design decision. Adding to a price commutes, so order does not matter here, but for streams, logging or HTML formatting (below) it does.

Understanding unique_ptr Movement

How std::move works: When you write std::move(coffee), you’re transferring ownership of the object from one unique_ptr to another.

std::unique_ptr<Coffee> coffee = std::make_unique<SimpleCoffee>();  // coffee owns SimpleCoffee
coffee = std::make_unique<MilkDecorator>(std::move(coffee));  
// 1. std::move(coffee) converts coffee to rvalue
// 2. MilkDecorator constructor takes ownership
// 3. Original coffee becomes nullptr

Step-by-step execution:

// Initial state
unique_ptr<Coffee> coffee -> [SimpleCoffee object]
// After std::move(coffee)
unique_ptr<Coffee> coffee -> nullptr
MilkDecorator constructor receives -> [SimpleCoffee object]
// After assignment
unique_ptr<Coffee> coffee -> [MilkDecorator object]
                                 |
                                 v
                            [SimpleCoffee object]

Memory ownership chain:

flowchart TD
    A["coffee (unique_ptr)"] --> B[MilkDecorator]
    B --> C[SimpleCoffee]
    
    style A fill:#e1f5ff
    style B fill:#fff4e1
    style C fill:#f0f0f0

The line coffee = std::make_unique<MilkDecorator>(std::move(coffee)); looks like it reads and writes the same variable in one statement, but it is safe. The right-hand side is fully evaluated first: the SimpleCoffee is moved into the new decorator’s member, leaving coffee empty. Only then is the new pointer move-assigned into coffee, and since coffee was already empty, nothing is destroyed.

Important: After std::move(), a moved-from unique_ptr is guaranteed to be nullptr. Dereferencing it is undefined behavior, which in practice is usually a null-pointer crash.

std::unique_ptr<Coffee> coffee = std::make_unique<SimpleCoffee>();
auto decorated = std::make_unique<MilkDecorator>(std::move(coffee));
// ❌ UB: coffee is now nullptr
coffee->cost();  // Crash!
// ✅ Use the new owner
decorated->cost();  // OK

Stream Decorator

Streams are the classic real-world use: Java’s BufferedInputStream(new FileInputStream(...)) is the textbook example, and in C++ the same idea appears in Boost.Iostreams filter chains. The encryption and compression here are toy stand-ins (string reversal and a prefix) so the example stays readable; the structure is what matters.

#include <iostream>
#include <memory>
#include <string>
#include <algorithm>
class DataStream {
public:
    virtual void write(const std::string& data) = 0;
    virtual std::string read() = 0;
    virtual ~DataStream() = default;
};
class FileStream : public DataStream {
public:
    void write(const std::string& data) override {
        buffer = data;
        std::cout << "[File] Written: " << data << '\n';
    }
    
    std::string read() override {
        std::cout << "[File] Reading\n";
        return buffer;
    }
    
private:
    std::string buffer;
};
class StreamDecorator : public DataStream {
public:
    StreamDecorator(std::unique_ptr<DataStream> s)
        : stream(std::move(s)) {}
    
protected:
    std::unique_ptr<DataStream> stream;
};
class EncryptionDecorator : public StreamDecorator {
public:
    using StreamDecorator::StreamDecorator;
    
    void write(const std::string& data) override {
        std::string encrypted = encrypt(data);
        std::cout << "[Encryption] Encrypting\n";
        stream->write(encrypted);
    }
    
    std::string read() override {
        std::string encrypted = stream->read();
        std::cout << "[Encryption] Decrypting\n";
        return decrypt(encrypted);
    }
    
private:
    std::string encrypt(const std::string& data) {
        std::string result = data;
        std::reverse(result.begin(), result.end());
        return result;
    }
    
    std::string decrypt(const std::string& data) {
        return encrypt(data);  // Symmetric
    }
};
class CompressionDecorator : public StreamDecorator {
public:
    using StreamDecorator::StreamDecorator;
    
    void write(const std::string& data) override {
        std::string compressed = compress(data);
        std::cout << "[Compression] Compressing\n";
        stream->write(compressed);
    }
    
    std::string read() override {
        std::string compressed = stream->read();
        std::cout << "[Compression] Decompressing\n";
        return decompress(compressed);
    }
    
private:
    std::string compress(const std::string& data) {
        return "[COMPRESSED]" + data;
    }
    
    std::string decompress(const std::string& data) {
        return data.substr(12);  // Remove "[COMPRESSED]"
    }
};
int main() {
    std::unique_ptr<DataStream> stream = std::make_unique<FileStream>();
    stream = std::make_unique<EncryptionDecorator>(std::move(stream));
    stream = std::make_unique<CompressionDecorator>(std::move(stream));
    
    stream->write("Hello, World!");
    std::string data = stream->read();
    std::cout << "Result: " << data << '\n';
}

The chain is Compression -> Encryption -> File. On write, the outermost layer runs first, so the data is compressed, then encrypted, then stored. On read, the data travels the other way: the file returns the stored bytes, encryption decrypts them, compression decompresses them. A decorator that transforms data must therefore apply its transformation before forwarding on the way in and after forwarding on the way out, otherwise the layers do not undo each other in the right order.

The order you wrap in is not cosmetic. Real compressors work by finding repetition, and good encryption output looks random, so encrypting first and compressing second gives you almost no compression. That is why the example wraps compression outside encryption: the caller’s data is compressed before it is encrypted. When I review decorator chains like this, a wrong wrapping order is the bug I look for first, because the code compiles and the round trip still works; it just silently loses the benefit of one layer.

decompress uses data.substr(12) because "[COMPRESSED]" is 12 characters. If the stored data is ever shorter than that (for example, a file written by a different chain), substr throws std::out_of_range. Real code would check the header before stripping it.


Logging System

Logging is where decorators earn their keep in application code: timestamps, severity levels, thread IDs, and filtering are independent concerns that you want to switch on and off per sink.

#include <iostream>
#include <memory>
#include <chrono>
#include <iomanip>
#include <sstream>
#include <string>
class Logger {
public:
    virtual void log(const std::string& message) = 0;
    virtual ~Logger() = default;
};
class ConsoleLogger : public Logger {
public:
    void log(const std::string& message) override {
        std::cout << message << '\n';
    }
};
class LoggerDecorator : public Logger {
public:
    LoggerDecorator(std::unique_ptr<Logger> l)
        : logger(std::move(l)) {}
    
protected:
    std::unique_ptr<Logger> logger;
};
class TimestampDecorator : public LoggerDecorator {
public:
    using LoggerDecorator::LoggerDecorator;
    
    void log(const std::string& message) override {
        auto now = std::chrono::system_clock::now();
        auto time = std::chrono::system_clock::to_time_t(now);
        std::ostringstream oss;
        oss << "[" << std::put_time(std::localtime(&time), "%Y-%m-%d %H:%M:%S") << "] ";
        logger->log(oss.str() + message);  // forward, don't print directly
    }
};
class LevelDecorator : public LoggerDecorator {
public:
    LevelDecorator(std::unique_ptr<Logger> l, const std::string& level)
        : LoggerDecorator(std::move(l)), level_(level) {}
    
    void log(const std::string& message) override {
        logger->log("[" + level_ + "] " + message);
    }
    
private:
    std::string level_;
};
int main() {
    std::unique_ptr<Logger> logger = std::make_unique<ConsoleLogger>();
    logger = std::make_unique<TimestampDecorator>(std::move(logger));
    logger = std::make_unique<LevelDecorator>(std::move(logger), "INFO");
    
    logger->log("Application started");
    // [2026-04-25 10:00:00] [INFO] Application started
}

LevelDecorator is outermost, so it prepends [INFO] and forwards; TimestampDecorator then prepends the time and forwards to the console. The resulting line has the timestamp first because the inner layer adds its prefix last.

An early version of this example had TimestampDecorator write the timestamp straight to std::cout and then forward the unchanged message. That works while the innermost logger is a console logger, and breaks the moment you swap in a file logger: the timestamp still goes to the terminal and the file gets lines without times. The rule that avoids this is simple: a decorator should only talk to the object it wraps, never to the outside world directly. Only the concrete component touches the real output.

Also note that std::localtime returns a pointer to shared static storage and is not thread-safe. In a multi-threaded logger use localtime_r (POSIX) or localtime_s (Windows), or C++20’s std::chrono::zoned_time with std::format.

LevelDecorator shows that decorators can take extra constructor arguments. It cannot use the inherited constructor, so it forwards the pointer to LoggerDecorator explicitly.


Common Errors and Solutions

Issue 1: Type Loss

Symptom: Once an object is wrapped, you can only call what the interface declares. Methods that exist only on the concrete class are out of reach.

std::unique_ptr<Coffee> coffee = std::make_unique<SimpleCoffee>();
coffee = std::make_unique<MilkDecorator>(std::move(coffee));
// ❌ Does not work: the outer object is a MilkDecorator, not a SimpleCoffee
if (auto* simple = dynamic_cast<SimpleCoffee*>(coffee.get())) {
    // never reached: dynamic_cast returns nullptr
}

dynamic_cast only checks the type of the outermost object. It cannot see through the wrapper, because the SimpleCoffee lives inside a protected member. If callers need a capability, the honest fix is to put it on the interface (possibly with a default implementation that decorators forward). If you find yourself needing to unwrap decorators often, that is a sign the feature is not a decorator concern and belongs somewhere else.

Issue 2: Order Dependency

Symptom: Results vary depending on the order of Decorators.

// Encryption -> Compression vs Compression -> Encryption
// Results may differ

Order matters whenever layers do not commute: compression and encryption (see section 2), HTML tags (section 6), or a filtering logger placed inside versus outside a timestamp logger. Build chains in one place (a builder or factory, see section 5) so the order is decided once rather than scattered across call sites.

Issue 3: Memory Management

Symptom: Forgetting to use std::move() causes compilation errors.

// ❌ Copy attempt (unique_ptr is not copyable)
std::unique_ptr<Coffee> coffee = std::make_unique<SimpleCoffee>();
auto bad = std::make_unique<MilkDecorator>(coffee);  // Error!
// ✅ Use std::move to transfer ownership
auto decorated = std::make_unique<MilkDecorator>(std::move(coffee));

Why unique_ptr?: unique_ptr ensures:

  1. Single ownership: Only one owner at a time
  2. Automatic cleanup: Destructor chain is called automatically
  3. No memory leaks: RAII guarantees cleanup even with exceptions Destructor chain:
{
    std::unique_ptr<Coffee> coffee = std::make_unique<SimpleCoffee>();
    coffee = std::make_unique<MilkDecorator>(std::move(coffee));
    coffee = std::make_unique<SugarDecorator>(std::move(coffee));
}  // Automatic cleanup from the outside in:
   // 1. ~SugarDecorator() body runs, then its member is destroyed
   // 2. ~MilkDecorator() body runs, then its member is destroyed
   // 3. ~SimpleCoffee()

This only works because Coffee has a virtual destructor. Without virtual ~Coffee() = default;, destroying a SugarDecorator through a unique_ptr<Coffee> would be undefined behavior, and in practice the inner layers would leak.

Manual memory management (not recommended):

// ❌ Raw pointers (error-prone)
Coffee* coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);  // Who owns the original?
delete coffee;  // Does this delete both?
// ✅ unique_ptr (safe)
std::unique_ptr<Coffee> coffee = std::make_unique<SimpleCoffee>();
coffee = std::make_unique<MilkDecorator>(std::move(coffee));
// Automatic cleanup, no leaks

(The raw-pointer version assumes a decorator constructor taking Coffee*; the answer to “does this delete both?” depends entirely on a convention nobody can see in the code, which is the problem.)

Issue 4: Use After Move

std::unique_ptr<Coffee> coffee = std::make_unique<SimpleCoffee>();
auto decorated = std::make_unique<MilkDecorator>(std::move(coffee));
// ❌ Use after move
std::cout << coffee->cost();  // UB! coffee is nullptr
// ✅ Use the new owner
std::cout << decorated->cost();  // OK

Clang-Tidy’s bugprone-use-after-move check catches most of these at build time; it is worth enabling in any codebase that passes ownership around this much.

Issue 5: Forgetting to Forward a Method

When the interface grows, every decorator has to forward the new method. If CoffeeDecorator declares no implementation, the compiler forces you to implement it in each concrete decorator (the class stays abstract otherwise). The dangerous case is when you give the base decorator a default that does not forward, or a decorator overrides a method and forgets to call the inner object: the chain silently stops at that layer. A useful habit is to have the abstract decorator base forward every method by default, so concrete decorators override only what they change.


Production Patterns

Pattern 1: Builder Style

class CoffeeBuilder {
    std::unique_ptr<Coffee> coffee;
public:
    CoffeeBuilder() : coffee(std::make_unique<SimpleCoffee>()) {}
    
    CoffeeBuilder& addMilk() {
        coffee = std::make_unique<MilkDecorator>(std::move(coffee));
        return *this;
    }
    
    CoffeeBuilder& addSugar() {
        coffee = std::make_unique<SugarDecorator>(std::move(coffee));
        return *this;
    }
    
    std::unique_ptr<Coffee> build() {
        return std::move(coffee);
    }
};
auto coffee = CoffeeBuilder()
    .addMilk()
    .addSugar()
    .build();

The builder hides std::move from callers and gives the chain a single construction point. build() leaves the builder empty, so calling it twice returns nullptr the second time; either document that or make build() &&-qualified so it can only be called on a temporary.

Pattern 2: Decorator with shared_ptr

// When multiple decorators need to share the same component
class SharedDecorator {
    std::shared_ptr<Coffee> coffee;
public:
    SharedDecorator(std::shared_ptr<Coffee> c) : coffee(c) {}
    
    std::shared_ptr<Coffee> getWrapped() {
        return coffee;  // Can share ownership
    }
};

Use shared_ptr only when two chains genuinely share an inner object, for example one sink written by two differently-decorated loggers. The cost is that the inner object’s lifetime is no longer obvious, and if an inner object ever holds a shared_ptr back to an outer one you have a reference cycle that never frees. Keep references pointing inward only.

Pattern 3: Decorator Factory

class DecoratorFactory {
public:
    static std::unique_ptr<Coffee> create(const std::string& recipe) {
        std::unique_ptr<Coffee> coffee = std::make_unique<SimpleCoffee>();
        
        if (recipe.find("milk") != std::string::npos) {
            coffee = std::make_unique<MilkDecorator>(std::move(coffee));
        }
        if (recipe.find("sugar") != std::string::npos) {
            coffee = std::make_unique<SugarDecorator>(std::move(coffee));
        }
        
        return coffee;
    }
};
// Usage
auto coffee = DecoratorFactory::create("milk sugar");

This is where the runtime flexibility pays off: the recipe can come from a config file or a user request, and the factory is the only place that knows the wrapping order. Note that the order of the if statements, not the order of words in the recipe string, decides the chain.

Alternative: Compile-Time Decoration

If the combination is known at compile time and the calls are on a hot path, each virtual layer costs an indirect call the compiler usually cannot inline. Templates can build the same stack statically: Sugar<Milk<SimpleCoffee>>, where each layer is a class template deriving from or holding its parameter. The compiler can then inline the whole chain. The trade-off is that the type changes with every combination, so you lose the ability to choose layers from data. See CRTP vs Virtual Functions for the static-polymorphism side of this trade-off.


Complete Example: Text Formatter

#include <iostream>
#include <memory>
#include <string>
#include <algorithm>
class TextFormatter {
public:
    virtual std::string format(const std::string& text) = 0;
    virtual ~TextFormatter() = default;
};
class PlainTextFormatter : public TextFormatter {
public:
    std::string format(const std::string& text) override {
        return text;
    }
};
class FormatterDecorator : public TextFormatter {
public:
    FormatterDecorator(std::unique_ptr<TextFormatter> f)
        : formatter(std::move(f)) {}
    
protected:
    std::unique_ptr<TextFormatter> formatter;
};
class BoldDecorator : public FormatterDecorator {
public:
    using FormatterDecorator::FormatterDecorator;
    
    std::string format(const std::string& text) override {
        return "<b>" + formatter->format(text) + "</b>";
    }
};
class ItalicDecorator : public FormatterDecorator {
public:
    using FormatterDecorator::FormatterDecorator;
    
    std::string format(const std::string& text) override {
        return "<i>" + formatter->format(text) + "</i>";
    }
};
class UpperCaseDecorator : public FormatterDecorator {
public:
    using FormatterDecorator::FormatterDecorator;
    
    std::string format(const std::string& text) override {
        std::string result = formatter->format(text);
        std::transform(result.begin(), result.end(), result.begin(), ::toupper);
        return result;
    }
};
int main() {
    std::unique_ptr<TextFormatter> formatter = std::make_unique<PlainTextFormatter>();
    formatter = std::make_unique<BoldDecorator>(std::move(formatter));
    formatter = std::make_unique<ItalicDecorator>(std::move(formatter));
    formatter = std::make_unique<UpperCaseDecorator>(std::move(formatter));
    
    std::cout << formatter->format("Hello, World!") << '\n';
    // <I><B>HELLO, WORLD!</B></I>
}

The output shows the order problem in its clearest form. UpperCaseDecorator is outermost, so it transforms the string after the inner layers have added their tags, and the tags come out as <I> and <B>. Browsers accept uppercase HTML tags, but an XML or XHTML consumer would not. Wrapping UpperCaseDecorator first (innermost, directly around PlainTextFormatter) uppercases only the text: <i><b>HELLO, WORLD!</b></i>.

std::transform with ::toupper also has a trap: passing a char with a negative value (any non-ASCII byte in UTF-8 text) to toupper is undefined behavior. The usual fix is a lambda that casts first: [](unsigned char c) { return std::toupper(c); }. For real Unicode case mapping you need a library such as ICU.


When a decorator is the right tool

The Decorator Pattern is a good fit when the extra behaviors are independent of each other and you want to pick them at runtime. When the combination is fixed and performance matters, prefer templates; when a “decorator” needs to know what the other layers are doing, the concern probably does not belong in a decorator at all.


FAQ

Q1: When should I use the Decorator Pattern?

A: Use it when you need to dynamically add features and there are too many combinations to handle with inheritance.

Q2: Inheritance vs Decorator?

A: Inheritance is static, while Decorator allows dynamic composition.

Q3: How is it different from Adapter and Proxy?

A: Adapter changes the interface so an object fits somewhere it otherwise would not. Decorator keeps the interface identical and adds behavior. Proxy also keeps the interface, but controls access to the object (lazy creation, remote calls, permission checks) rather than adding features, and usually manages the real object’s lifetime itself instead of receiving it from the caller.

Q4: What about performance overhead?

A: A long chain of Decorators can increase indirect references. Each decorator adds one virtual function call. For performance-critical code, consider:

  • Limiting decorator depth
  • Using CRTP for compile-time decoration
  • Profiling to identify bottlenecks

Q5: How do I handle type loss?

A: dynamic_cast only sees the outermost layer, so it cannot recover a wrapped object. Design the interface to expose the functionality callers need, or keep a separate non-owning pointer to the concrete object if you created it and must reach it later.

Q6: Why use unique_ptr instead of raw pointers?

A: unique_ptr provides:

  • Automatic memory management: No manual delete needed
  • Exception safety: Cleanup guaranteed even with exceptions
  • Clear ownership: Single owner, transfer with std::move()
  • Zero overhead: Same performance as raw pointers

Q7: Can I use shared_ptr instead of unique_ptr?

A: Yes, if you need shared ownership. However, unique_ptr is preferred for decorators because:

  • Decorators typically have single ownership
  • unique_ptr is more efficient (no reference counting)
  • Ownership transfer is explicit with std::move()

Q8: What happens if I forget std::move()?

A: Compilation error. unique_ptr is not copyable, only movable. This prevents accidental ownership duplication.

std::unique_ptr<Coffee> coffee = std::make_unique<SimpleCoffee>();
auto decorated = std::make_unique<MilkDecorator>(coffee);  // Error!
// clang: error: call to deleted constructor of 'std::unique_ptr<Coffee>'
// gcc:   error: use of deleted function 'std::unique_ptr<...>::unique_ptr(const std::unique_ptr<...>&)'

Q9: How do I debug decorator chains?

A: Add logging to each decorator’s constructor and methods to trace the execution flow:

class DebugDecorator : public CoffeeDecorator {
public:
    DebugDecorator(std::unique_ptr<Coffee> c) 
        : CoffeeDecorator(std::move(c)) {
        std::cout << "[Debug] DebugDecorator created\n";
    }
    
    std::string getDescription() const override {
        std::cout << "[Debug] getDescription called\n";
        return coffee->getDescription();
    }
};

Q10: Any resources to learn the Decorator Pattern?

A:

The Decorator Pattern allows you to dynamically combine features using composition and unique_ptr for safe memory management.