直接用模板参数推导observer接口会编译失败,因硬编码t::notify()触发sfinae失败且无法约束合法性;正确做法是显式传入通知签名(如void(t&)),用observer_base实现静态多态,并由subject遍历调用update()从自身提取状态。

为什么直接用模板参数推导 Observer 接口会编译失败
常见错误是试图让 Observer 模板类自动推导被观察类型,比如写成 template<typename t> class Observer</typename> 然后在内部硬编码调用 T::notify() —— 这样做会在 T 尚未定义或不提供该接口时触发 SFINAE 失败,且无法约束调用合法性。真正可行的路径是把「通知签名」作为模板参数显式传入,而非依赖具体类型是否带某个成员函数。
推荐做法:用函数类型(如 void(T&))或可调用对象概念约束观察者行为,再通过 std::function 或 std::variant 存储异构观察者。但若追求零开销,就用类型擦除 + 可变参数模板实现静态多态:
-
template<typename signature> struct observer_base;</typename>以函数签名锚定契约 - 每个具体
Observer继承自对应签名的observer_base<void></void> - 注册时用
static_cast向基类指针转型,避免虚函数开销
如何让 Subject 支持多种通知签名而不重复写 notify() 逻辑
关键不是为每种参数列表重载 notify(),而是用参数包展开 + 成员函数指针转发。典型错误是写一堆 notify(int)、notify(const std::string&) 重载——这会让 Subject 耦合具体业务语义。
正确做法是把通知逻辑下沉到观察者侧:Subject 只负责遍历并调用 update(),而 update() 的参数由观察者自己从 Subject 中提取。例如:
template<typename t>
class Subject {
std::vector<observer_base>*> observers_;
public:
void notify() {
for (auto* obs : observers_) obs->update(static_cast<t>(*this));
}
};</t></observer_base></typename>
这样 T 必须可被 static_cast 到自身,且所有观察者都约定从 T& 中读取所需状态。比硬编码参数更灵活,也避免模板爆炸。
std::function vs. 函数对象模板:性能与泛化怎么选
用 std::function<void></void> 存储观察者最简单,但带来堆分配和虚调用开销;用 template<typename f> register_observer(F&& f)</typename> 能内联,却导致 Subject 实例膨胀(每个 lambda 类型生成一份 notify 代码)。
折中方案是组合二者:
- 对高频小函数(如日志打印),用函数对象模板注册,保证内联
- 对动态构造的回调(如 GUI 事件绑定),走
std::function分支 - 用
std::variant<:function>, std::unique_ptr<...>></...></:function>统一容器,配合访问者模式 dispatch
注意:C++20 的 std::move_only_function 在移动捕获 lambda 场景下更安全,但需确认编译器支持(GCC 13+/Clang 16+)。
模板递归注册导致编译时间爆炸的真实原因
有人尝试用 template<typename... obs> struct Subject : Obs...</typename...> 让 Subject 直接继承多个观察者,再用 ADL 找 update() —— 这会导致模板实例化深度随观察者数量指数增长,Clang 很快报 constexpr evaluation hit limit 或内存耗尽。
根本问题在于继承链引入了隐式依赖关系,编译器必须为每个组合生成独立 vtable 和偏移量计算逻辑。替代方案只有两个:
- 坚持聚合而非继承:Subject 持有观察者列表,观察者持有指向 Subject 的弱引用(
std::weak_ptr或裸指针 + 生命周期管理协议) - 用
boost::hana::tuple或 C++23std::tuple配合 fold 表达式展开注册,但依然要避免嵌套模板参数推导
实际项目里,超过 5 个不同类型的观察者就该考虑运行时类型擦除,而不是硬撑编译期泛化。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











