C++可通过虚函数接口与包装类实现装饰器模式;必须用纯虚函数定义Component接口以支持运行时多态和多层叠加,装饰器通过std::unique_ptr持有被装饰对象并转发调用,需严格管理所有权避免悬空指针。

C++ 没有原生装饰器语法,但用包装类 + 虚函数接口完全能实现等效效果;关键不是“像 Python 那样写 @decorator”,而是让扩展行为可组合、不侵入原始逻辑。
为什么必须用虚函数定义 Component 接口
装饰器要和被装饰对象“看起来一样”,就得共享同一接口。如果只靠模板或继承具体类,会失去运行时替换能力,也无法叠加多层装饰(比如 LoggingDecorator 包一层,再包一层 RetryDecorator)。
- 基类
Component必须声明纯虚函数(如virtual void execute() = 0),否则派生类无法被统一指针管理 - 不能把接口函数设为
final或非虚——否则ConcreteDecorator无法重写行为 - 若用值语义(非指针/引用)传递,多态失效;必须用
std::unique_ptr<Component>或Component&持有
装饰器类如何正确转发并增强调用
每个装饰器内部持有一个 std::unique_ptr<Component>,构造时接收被装饰对象;重写的函数里先执行增强逻辑(如日志、计时),再调用 m_component->execute() —— 这是核心转发逻辑。
- 别在装饰器构造函数里直接调用
m_component->execute(),那是初始化阶段,不是使用阶段 - 若需访问被装饰对象的特有接口(非
Component定义的),说明设计已泄漏,应提取共用接口或用 visitor 补充 - 避免裸指针持有:
Decorator析构时自动释放m_component,防止内存泄漏
class LoggingDecorator : public Component {
std::unique_ptr<Component> m_component;
public:
explicit LoggingDecorator(std::unique_ptr<Component> comp)
: m_component(std::move(comp)) {}
void execute() override {
std::cout << "[LOG] start\n";
m_component->execute(); // 关键:转发给下一层
std::cout << "[LOG] done\n";
}
};
动态组合时容易忽略的生命周期与所有权问题
装饰器链本质是单向所有权链:OuterDecor → InnerDecor → Concrete。谁 new 谁 delete 不重要,重要的是谁拥有谁。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
立即学习“C++免费学习笔记(深入)”;
- 用
std::make_unique创建最内层对象,然后逐层包裹:auto ptr = std::make_unique<ConcreteComponent>();ptr = std::make_unique<LoggingDecorator>(std::move(ptr));ptr = std::make_unique<TimingDecorator>(std::move(ptr)); - 切勿用栈对象地址去构造装饰器(如
ConcreteComponent c; LoggingDecorator d(&c);)—— 装饰器析构后,内部指针悬空 - 如果需要共享底层对象(比如多个装饰器共用一个
Component),改用std::shared_ptr,但要注意循环引用风险
不推荐用模板替代虚函数实现“静态装饰器”
有人尝试用模板参数传入被装饰类型,生成编译期组合(类似 CRTP)。这看似零开销,但实际破坏了装饰器的核心价值:
- 无法运行时决定加哪几层(比如配置文件控制是否启用重试)
- 每种组合产生独立类型,容器无法统一存放(
std::vector<Component*>失效) - 错误信息里全是嵌套模板名,调试困难;且编译时间随组合爆炸增长
- 只有当装饰逻辑极度简单、层数固定、且明确拒绝运行时灵活性时,才考虑模板方案
真正难的不是写出来,是想清楚哪部分必须运行时可插拔——那部分就坚决用虚函数;其余可优化的细节(如缓存、内联)再另说。

















