c++不支持python式装饰器,因其缺乏运行时反射和函数包装元操作,所有装饰必须通过模板包装、crtp继承或组合实现,无法动态注入且编译期固化。

C++ 没有语言级装饰器,所谓“类装饰器”是手动模拟的设计模式,不是 Python 那种 @decorator 语法。
为什么 C++ 不能直接写 @log_calls 这类装饰器
C++ 编译期不支持运行时可插拔的函数包装元操作,也没有反射机制自动拦截成员调用。所有“装饰”行为必须显式构造、组合或继承,本质是编译期静态绑定。
常见错误现象:error: expected unqualified-id before ‘@’ token —— 直接照搬 Python 写法会编译失败。
- 装饰逻辑只能靠组合(has-a)或模板包装实现,不能注入到原类定义里
- 无法在不修改调用方代码的前提下,给已有对象“动态加日志/权限/缓存”
- 宏模拟(如
DECORATE_WITH_LOG(MyClass))只是代码生成,不是真正装饰器语义
用模板包装器模拟装饰器(推荐做法)
核心思路:写一个通用模板类,把目标对象包进去,重载接口函数,在前后插入横切逻辑。
立即学习“C++免费学习笔记(深入)”;
示例场景:给任意带 process() 成员函数的对象添加计时功能:
template<typename T>
class TimingDecorator {
T obj;
public:
TimingDecorator(T o) : obj(std::move(o)) {}
auto process() {
auto start = std::chrono::steady_clock::now();
auto result = obj.process();
auto end = std::chrono::steady_clock::now();
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
std::cout << "process took " << ms << "ms\n";
return result;
}
};
- 要求被装饰类型
T必须公开提供process(),且签名一致 - 性能影响:零成本抽象(如果内联成功),但每次调用多一层栈帧和时钟开销
- 不兼容虚函数多态——
TimingDecorator<base>无法工作,得用指针模板特化或 PIMPL
用继承 + CRTP 实现静态装饰(适合固定扩展点)
当装饰行为高度结构化(比如统一加 validate() 前置检查),CRTP 能避免虚函数开销,且支持编译期定制。
使用场景:为多个业务类统一添加输入校验,但每个类校验逻辑不同。
template<typename Derived>
class ValidatingDecorator {
public:
void execute() {
static_cast<Derived*>(this)->validate(); // 编译期绑定
static_cast<Derived*>(this)->do_work();
}
};
<p>class MyService : public ValidatingDecorator<MyService> {
public:
void validate() { /<em> 具体校验 </em>/ }
void do_work() { /<em> 实际逻辑 </em>/ }
};</p>- CRTP 的
Derived必须显式传入,不能自动推导;漏写会导致基类调用纯虚或未定义行为 - 无法运行时切换装饰行为(比如根据配置关掉校验),所有逻辑在编译期固化
- 调试困难:堆栈里会出现冗长的模板实例名,如
ValidatingDecorator<myservice>::execute</myservice>
别碰宏模拟装饰器(除非你控制整个构建链)
有人用宏生成包装类,看起来像 DECORATE(MyClass, Log, Cache),但实际是预处理器文本替换。
容易踩的坑:
- 宏展开后破坏 IDE 跳转和类型提示,
gdb调试时看不到原始函数名 - 宏参数含逗号(如
std::vector<int></int>)会触发分隔错误,必须用括号包裹 - 和模板、constexpr 冲突严重,C++20 概念约束几乎无法与这类宏共存
- 团队协作中,新成员看到
DECORATE第一反应是查文档,而不是理解它做了什么
真正麻烦的地方不在怎么写,而在于决定“哪些逻辑值得抽成装饰器”。缓存、日志、权限这些看似通用,但一旦涉及生命周期(比如装饰器持有 std::shared_ptr<cache></cache>)、线程安全(装饰器内部加锁粒度)、或错误传播(装饰器抛异常是否吞掉原错误),就迅速变成需要全局权衡的架构决策。没想清楚边界前,先用普通函数包装,比强行套装饰器模式更稳妥。










