如何将成员函数的模板参数作为成员变量存储并后续调用?
实现无虚函数、无std::function的可调用对象存储Wrapper
问题分析
你当前代码的核心问题在于HandlerWrapper<Callable>是模板类,不同Callable对应的HandlerWrapper是完全独立的类型,无法用std::unique_ptr<HandlerWrapper<void()>>统一存储。而你又限制了不能用虚基类和std::function,所以需要用无虚函数的类型擦除方案——这也是Boost.Asio异步操作的核心实现思路之一。
可行实现方案
我们可以通过存储void*类型的对象指针,搭配两个函数指针(分别负责调用可调用对象和销毁对象)来实现类型擦除,完全不需要虚函数或std::function:
#include <iostream> #include <memory> #include <utility> #include <functional> struct MyWrapper { // 析构时清理存储的对象 ~MyWrapper() { if (destroy_fn) { destroy_fn(obj_ptr); } } // 禁止拷贝,允许移动 MyWrapper(const MyWrapper&) = delete; MyWrapper& operator=(const MyWrapper&) = delete; MyWrapper(MyWrapper&& other) noexcept : obj_ptr(other.obj_ptr), invoke_fn(other.invoke_fn), destroy_fn(other.destroy_fn) { other.obj_ptr = nullptr; other.invoke_fn = nullptr; other.destroy_fn = nullptr; } MyWrapper& operator=(MyWrapper&& other) noexcept { if (this != &other) { // 先清理当前对象 if (destroy_fn) { destroy_fn(obj_ptr); } obj_ptr = other.obj_ptr; invoke_fn = other.invoke_fn; destroy_fn = other.destroy_fn; other.obj_ptr = nullptr; other.invoke_fn = nullptr; other.destroy_fn = nullptr; } return *this; } MyWrapper() = default; template <typename T> void set(T&& handle) { // 先清理之前的对象 if (destroy_fn) { destroy_fn(obj_ptr); } // 分配存储对象的内存,完美转发handle using HandlerType = std::decay_t<T>; obj_ptr = new HandlerType(std::forward<T>(handle)); // 绑定调用和销毁的函数指针 invoke_fn = [](void* ptr) { (*static_cast<HandlerType*>(ptr))(); }; destroy_fn = [](void* ptr) { delete static_cast<HandlerType*>(ptr); }; } void invoke() { if (invoke_fn) { invoke_fn(obj_ptr); } } private: void* obj_ptr = nullptr; void (*invoke_fn)(void*) = nullptr; void (*destroy_fn)(void*) = nullptr; }; struct RandomStruct { RandomStruct(std::unique_ptr<int>&& ptr) : ptr_(std::move(ptr)) {} void operator()() { std::cout << "Handler invoked with value: " << *ptr_ << std::endl; } std::unique_ptr<int> ptr_; }; int main() { MyWrapper op; // 使用场景1:带移动捕获的lambda op.set([ptr = std::make_unique<int>(2)]() { std::cout << "Handler invoked with value: " << *ptr << std::endl; }); op.invoke(); // 使用场景2:自定义可调用类 RandomStruct rs(std::make_unique<int>(3)); op.set(std::move(rs)); op.invoke(); // 使用场景3:std::function<void()> std::function<void()> fn = []() { std::cout << "std::function handler invoked" << std::endl; }; op.set(fn); op.invoke(); return 0; }
方案说明
- 类型擦除原理:通过
void*存储任意类型的可调用对象,同时用两个函数指针分别封装“调用对象”和“销毁对象”的逻辑,这两个函数指针在模板的set函数中被实例化,对应具体的可调用类型。 - 内存安全:析构函数、移动赋值/构造中都会正确清理之前的对象,避免内存泄漏;同时禁止拷贝,防止浅拷贝导致的重复销毁问题。
- 兼容所有场景:完美支持带捕获的lambda、自定义可调用类、
std::function<void()>,符合你的需求。 - 与Boost.Asio的关联:Boost.Asio的异步操作正是用类似的方式,将用户提供的可调用对象打包成
handler,通过类型擦除实现任意可调用对象的存储和延迟调用,避免虚函数的开销。
原代码问题修正
你之前的思路方向是对的(尝试用包装类封装可调用对象),但错误在于试图用HandlerWrapper<void()>作为统一基类,而模板类的不同实例之间没有继承关系,无法统一存储。改用void*+函数指针的类型擦除方式就解决了这个问题。
内容的提问来源于stack exchange,提问作者nanika
相关产品推荐
相关产品推荐

