从JavaScript迁移到C++:如何缓存捕获this指针的回调?
解决方案
方案一:手动实现类型擦除(替代std::function)
如果不想用std::function,可以通过多态+模板包装器实现类型擦除,让leaf类接受任意可调用对象,包括捕获this的lambda:
修改后的完整代码
#include <vector> #include <cstdio> #include <memory> // 抽象回调基类,定义统一调用接口 class Callback { public: virtual ~Callback() = default; virtual void operator()() = 0; }; // 模板包装器,适配任意可调用对象 template<typename Cb> class CallbackWrapper : public Callback { private: Cb cb_; public: explicit CallbackWrapper(Cb cb) : cb_(std::move(cb)) {} void operator()() override { cb_(); } }; class leaf { public: // 模板注册函数,自动推导回调类型并包装 template<std::invocable Cb> void register_callback(Cb cb) { callbacks_.emplace_back(std::make_unique<CallbackWrapper<Cb>>(std::move(cb))); } void do_leave() { for (auto& cb : callbacks_) { (*cb)(); } } private: std::vector<std::unique_ptr<Callback>> callbacks_; }; class tree { public: tree() { myleaf_.register_callback([this](){ do_some_messageing(); }); } void do_some_messageing() { printf("Hello World\n"); } private: leaf myleaf_; // 无需模板参数 }; int main() { tree Tree1; Tree1.myleaf_.do_leave(); // 输出:Hello World }
原理说明
- 抽象基类
Callback定义统一调用接口,leaf只需存储基类指针,无需关心具体回调类型; - 模板类
CallbackWrapper将任意可调用对象(包括捕获this的lambda)包装成基类的派生实例; leaf::register_callback是模板函数,自动推导传入的回调类型,完成包装后存入容器。
这种方式既解决了lambda类型无法提前确定的问题,也允许不同对象向同一个leaf注册回调。
方案二:函数指针+上下文指针(无动态分配)
如果完全不想用动态内存,可以用函数指针+void*上下文的方式手动处理this捕获,代价是类型安全性降低:
修改后的完整代码
#include <vector> #include <cstdio> #include <utility> class leaf { public: using CallbackFunc = void(*)(void*); void register_callback(CallbackFunc func, void* ctx) { callbacks_.emplace_back(func, ctx); } void do_leave() { for (auto& [func, ctx] : callbacks_) { func(ctx); } } private: std::vector<std::pair<CallbackFunc, void*>> callbacks_; }; class tree { public: tree() { // 把成员函数包装成静态逻辑,通过void*传递this myleaf_.register_callback( [](void* ctx){ static_cast<tree*>(ctx)->do_some_messageing(); }, this ); } void do_some_messageing() { printf("Hello World\n"); } private: leaf myleaf_; }; int main() { tree Tree1; Tree1.myleaf_.do_leave(); // 输出:Hello World }
原理说明
leaf存储函数指针和对应的上下文指针(void*);- 注册回调时,将捕获
this的逻辑转换成无捕获lambda(或静态函数),把this作为上下文参数传入; - 调用回调时,通过
void*将上下文转换回原对象类型,再调用成员函数。
这种方式没有动态分配开销,但需要手动处理类型转换,适合对性能要求极高且能保证类型安全的场景。
方案三:固定回调类型(仅适用于单一回调场景)
如果leaf只需要接收tree类的回调,可以通过decltype推导lambda类型,提前确定leaf的模板参数:
修改后的完整代码
#include <vector> #include <cstdio> #include <utility> template <std::invocable Cb> class leaf { public: void register_callback(Cb cb) { callbacks_.push_back(std::move(cb)); } void do_leave() { for (auto& cb : callbacks_) { cb(); } } private: std::vector<Cb> callbacks_; }; class tree { private: // 提前定义回调生成函数,用于推导类型 auto create_callback() { return [this](){ do_some_messageing(); }; } public: // 用decltype推导回调类型 using CallbackType = decltype(std::declval<tree>().create_callback()); tree() { myleaf_.register_callback(create_callback()); } void do_some_messageing() { printf("Hello World\n"); } private: leaf<CallbackType> myleaf_; }; int main() { tree Tree1; Tree1.myleaf_.do_leave(); // 输出:Hello World }
原理说明
- 通过
create_callback函数提前暴露lambda的类型,用decltype推导得到CallbackType; leaf的模板参数指定为CallbackType,解决了声明myleaf_时的类型未知问题;- 每个
tree对象的lambda捕获不同的this,但类型一致,可存入vector<CallbackType>。
但此方案局限性大:leaf只能接收这种特定类型的回调,无法兼容其他对象的回调。
内容的提问来源于stack exchange,提问作者glades
相关产品推荐
相关产品推荐

