You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将成员函数的模板参数作为成员变量存储并后续调用?

实现无虚函数、无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;
}

方案说明

  1. 类型擦除原理:通过void*存储任意类型的可调用对象,同时用两个函数指针分别封装“调用对象”和“销毁对象”的逻辑,这两个函数指针在模板的set函数中被实例化,对应具体的可调用类型。
  2. 内存安全:析构函数、移动赋值/构造中都会正确清理之前的对象,避免内存泄漏;同时禁止拷贝,防止浅拷贝导致的重复销毁问题。
  3. 兼容所有场景:完美支持带捕获的lambda、自定义可调用类、std::function<void()>,符合你的需求。
  4. 与Boost.Asio的关联:Boost.Asio的异步操作正是用类似的方式,将用户提供的可调用对象打包成handler,通过类型擦除实现任意可调用对象的存储和延迟调用,避免虚函数的开销。

原代码问题修正

你之前的思路方向是对的(尝试用包装类封装可调用对象),但错误在于试图用HandlerWrapper<void()>作为统一基类,而模板类的不同实例之间没有继承关系,无法统一存储。改用void*+函数指针的类型擦除方式就解决了这个问题。

内容的提问来源于stack exchange,提问作者nanika

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 20:24:51