C++变参Callback模板封装和CallbackResponder

更多精彩内容,欢迎关注作者微信公众号:码工笔记

最近在使用C++实现一个功能的时候,遇到了一个需要封装变参callback类型的场景,特别记录一下。

基本思路就是有一个void(*)()的指针用来做方法地址的存储,然后使用一个模板定义参数和返回值类型,并在真正调用时,将存储的方法地址cast成模板定义的方法类型,如下:

CPP 复制代码
class CallbackBase {
public:
    using InvokeFuncStorage = void(*)();
    
    CallbackBase(InvokeFuncStorage func) : func_(func) {};
    
protected:
    InvokeFuncStorage func_;
};

template <typename R, typename... Args>
class Callback;

template <typename R, typename... Args>
class Callback<R(Args...)> : public CallbackBase {
public:
    using RunType = R(Args...);
    using PolymorphicInvoke = R (*)(Args...);
    
    Callback() : CallbackBase(nullptr) {};
    Callback(PolymorphicInvoke func) : CallbackBase((CallbackBase::InvokeFuncStorage)func) {}
    
    R run(Args... args) {
        auto func = reinterpret_cast<PolymorphicInvoke>(func_);
        return func(std::forward<Args>(args)...);
    }
};

如果处理回调的地方可以直接存储形如Callback<void(int)>强类型的实例,则问题解决,回调数据回来的时候直接调用callback.run(intData)即可。

但如果存储callback实例的是一个公共的地方(如一个CallbackManager类),且CallbackManager存储了各种类型的callback实例(各个callback实例的模板参数不同),该如何统一存储呢?存储CallbackBase能解决问题吗?在回调数据到达CallbackManager时,CallbackManager该如何调用回调呢?怎样才能知道该将CallbackBase cast成什么子类呢?

细化一下此问题:

  • CallbackManager需要存储各种模板类型的Callback实例
    • 比如其中有一个类型为Callback<void(int)>,即接收一个int参数,并且假设其callbackId为123
  • CallbackManager会在不同时机收到(callbackId,callbackData)的二元组,其中callbackData为序列化后的回调数据
    • 本例中序列化数据(callbackData)为string类型
  • CallbackManager需要根据callbackId(如123)找到相应的callback(此例中为Callback<void(int)>实例),并根据callback的模板参数类型(本例中为一个参数,int型)将callbackData(本例中为string类型)反序列化,并对其调用callback方法。

要解决此问题还需要多一层封装,即对每个Callback类型封装一个CallbackResponder类,如上例中的Callback<void(int>)需要被封装到一个MyCallbackResponder类中:

c 复制代码
class MyCallbackResponder : public CallbackResponder {
public:
    MyCallbackResponder(std::string callbackId, Callback<void(int)>callback) : callbackId_(callbackId), callback_(std::move(callback)) {}
    
    virtual void onMessage(const std::string &msg) override {
        //反序列化,把general消息转换为强类型参数x, y
        Serializer serializer(msg);
        int x = serializer.deserialize("x");
        
        //调用callback
        callback_.run(x);
    }
    
private:
    std::string callbackId_;
    Callback<void(int)> callback_;
};

其中 ICallbackResponder 形如:

csharp 复制代码
class CallbackResponder {
public:
    virtual void onMessage(std::string msg) = 0;
}

CallbackManager中存储的是 CallbackResponder 类型的实例列表:

c 复制代码
class CallbackManager {
public:
    void onCallback(const std::string &callbackId, const std::sring &callbackData) {
        if (responders_.find(callbackId) != responders_.end()) {
            responders_[callbackId]->onMessage(callbackData);
        }
    }


private:
    std::unordered_map<std::string, std::unique_ptr<CallbackResponder>> responders_;
}

CallbackManager 在 onCallback 方法中只要找到相应的 callbackResponder,然后调用其onMessage方法即可。

相关推荐
前端之虎陈随易4 小时前
编程语言级别的Skill市场,AI Agent 的未来形态
前端·vue.js·人工智能·typescript·node.js
一路向北he4 小时前
字节钢铁军团--“提供情境,而非控制”
java·开发语言·前端
kyriewen5 小时前
豆包和千问同时关了智能体,我用它们搭的 3 个自动化全废了——迁移方案整理
前端·javascript·ai编程
前端一小卒5 小时前
我用 TypeScript 从零手写了一个 Claude Code,然后发现它的核心只有 30 行
前端·agent
GitLqr5 小时前
Flutter 3.44 插件内置 Kotlin (KGP) 双向兼容适配指南
android·flutter·dart
大圣编程7 小时前
Python中continue语句的用法是什么?
开发语言·前端·python
yuhaiqiang7 小时前
随手 vibecoding 的浏览器插件已经 6000 多次下载,聊聊他的产品设计
前端·后端·面试
之歆7 小时前
Vue商品详情与放大镜组件
前端·javascript·vue.js
再吃一根胡萝卜8 小时前
如何把小米 MiMo 接入 CodeBuddy,打造私有 Agent
前端
负责的蛋挞9 小时前
异步HttpModule的实现方式
java·服务器·前端