C++ 通用回调实现

Posted

技术标签:

【中文标题】C++ 通用回调实现【英文标题】:C++ generic callback implementation 【发布时间】:2021-05-25 12:40:08 【问题描述】:

我有一个代码,它以 XML 的形式从 flash player 获取消息,将它们解析为函数和参数,并为该函数调用注册的回调。 我要替换的那段代码做得很好(几乎)通用回调机制: code for the generic callback implementation of flashSDK (ASInterface.inl).

问题在于这段代码是为 flash 编写的,我想替换 flash 并使用具有相同接口的其他服务。这种回调机制是否有任何标准实现(std?boost?其他开源的东西?)?

此代码实现了通用回调机制,您可以在映射中注册具有多个参数和类型的函数:

void SomethingHappened(int a, int b) print a + b;
void SomethingElseHappened(string abcd) print abcd;
callbacks["SomethingHappened"] = &SomethingHappened;
callbacks["SomethingElseHappened"] = &SomethingElseHappened;

然后搜索它并使用参数数组调用:

Callbacks::iterator itCallback = callbacks.find(functionName);
if (itCallback != callbacks.end())

    HRESULT result = itCallback->second.Call(arguments, returnValue);

完整用法示例:

//init callbacks
std::map<std::wstring, Callback> callbacks;
void SomethingHappened(int a, int b) print a + b;
void SomethingElseHappened(string abcd) print abcd;
callbacks[functionName] = &SomethingHappened;

void MessageArrived(string xmlInput)

    string functionName = parseFunctionName(xmlInput);
    Callbacks::iterator itCallback = callbacks.find(functionName);
    if (itCallback != callbacks.end())
    
        //parse arguments
        std::vector<std::wstring> args;
        _Args::split(xml, args);
        ASValue::Array arguments;
        for (size_t i = 0, s = args.size(); i < s; ++i)
        
            ASValue arg; arg.FromXML(args[i]);
            arguments.push_back(arg);
        
        ASValue returnValue;
        //***this is where the magic happens: call the function***
        HRESULT result = itCallback->second.Call(arguments, returnValue);
        return result;
    

【问题讨论】:

看来你想要std::function&lt;HRESULT(ASValue::Array, ASValue&amp;)&gt; @Jarod42 不,因为我已经拥有以下功能:SomethingHappened、SomethingElseHappened 等等,它们有不同的声明 你需要一个包装器来创建std::function,不是吗? 什么是ASValue?某种std::variant&lt;int, string, double, ASValue::Array, ...&gt;? 我不熟悉 std::variant。虽然 ASValue 保存基本类型数据。请参阅此链接:github.com/cpzhang/bud/blob/… 【参考方案1】:

你可以实现类似的东西。 包含 std::function&lt;R(Args...)&gt; 对象的对象映射(此处为 GenericCallback),使用 std::anystd::variant 进行类型擦除。

您需要小心调用函数回调的方式。

例如我必须给它一个 std::string("hello world") 而不是简单的 C 字符串,否则 std::any_cast 会抛出(因为 function&lt;string(const char*)&gt; 不是 function&lt;string(string)&gt;)。

#include <algorithm>
#include <any>
#include <functional>
#include <iostream>
#include <string>
#include <map>
#include <memory>

struct Caller 
    virtual ~Caller() = default;
    virtual std::any call(const std::vector<std::any>& args) = 0;
;


template<typename R, typename... A>
struct Caller_: Caller 

    template <size_t... Is>
    auto make_tuple_impl(const std::vector<std::any>& anyArgs, std::index_sequence<Is...> ) 
        return std::make_tuple(std::any_cast<std::decay_t<decltype(std::get<Is>(args))>>(anyArgs.at(Is))...);
    

    template <size_t N>
    auto make_tuple(const std::vector<std::any>& anyArgs) 
        return make_tuple_impl(anyArgs, std::make_index_sequence<N> );
    

    std::any call(const std::vector<std::any>& anyArgs) override 
        args = make_tuple<sizeof...(A)>(anyArgs);
        ret = std::apply(func, args);
        return ret;
    ;

    Caller_(std::function<R(A...)>& func_)
    : func(func_)
    

    std::function<R(A...)>& func;
    std::tuple<A...> args;
    R ret;
;

struct GenericCallback 

    template <class R, class... A>
    GenericCallback& operator=(std::function<R(A...)>&& func_) 
        func = std::move(func_);
        caller = std::make_unique<Caller_<R, A...>>(std::any_cast<std::function<R(A...)>&>(func));
        return *this;
    

    template <class Func>
    GenericCallback& operator=(Func&& func_) 
        return *this = std::function(std::forward<Func>(func_));
    

    std::any callAny(const std::vector<std::any>& args) 
        return caller->call(args);
    

    template <class R, class... Args>
    R call(Args&&... args) 
        auto& f = std::any_cast<std::function<R(Args...)>&>(func);
        return f(std::forward<Args>(args)...);
    

    std::any func;
    std::unique_ptr<Caller> caller;
;

using namespace std;

//Global functions
int sub(int a, int b)  return a - b; 
std::function mul = [](int a, int b)  return a*b;;
std::string sortString(std::string str)  
    std::sort(str.begin(), str.end()); 
    return str;  


int main() 

    std::map<std::string, GenericCallback> callbacks;

    // Adding our callbacks

    callbacks["add"] = [](int a, int b)  return a + b; ;
    callbacks["sub"] = sub;
    callbacks["mul"] = std::move(mul);
    callbacks["sortStr"] = sortString;

    // Calling them (hardcoded params)

    std::cout << callbacks["add"].call<int>(2, 3) << std::endl;
    std::cout << callbacks["sub"].call<int>(4, 2) << std::endl;
    std::cout << callbacks["mul"].call<int>(5, 6) << std::endl;
    std::cout << callbacks["sortStr"].call<std::string>(std::string("hello world")) << std::endl;

    // Calling "add" (vector of any params)

    std::vector<std::any> args =  1, 2 ;
    std::any result = callbacks["add"].callAny(args);

    std::cout << "result=" << std::any_cast<int>(result) << std::endl;

    return 0;

https://godbolt.org/z/h63job

【讨论】:

但是在您的调用中,您正在硬编码它的接口回调[“add”].call(2, 3),我需要将任意数量的参数传递给回调[ function].call(args),它会自己映射参数 如果您在编译时不知道args 后面的类型是什么(这意味着您打算将其设为vector&lt;any&gt; 或类似的类型?)那么将会有一些额外的工作在你的论据包上做,是的。就像使用 std::holds_alternative&lt;T&gt;(anyVar) 检查它们的类型,使用 std::anyCast&lt;T&amp;&gt;(anyVar) 强制转换它们并将它们传递给您注册的函数一样。 我在编译时确实知道 arg 背后的类型,但 callbacks[str].call 的使用应该以与 sortStr 和任何其他函数签名相同的方式完成......看看此代码:github.com/cpzhang/bud/blob/… 看起来他们所有的回调签名都是一样的:std::any func(std::vector&lt;std::any&gt; args, std::any),或者与其变体类型ASValue的等价物。这使他们的工作变得更加容易,但这不是您在原始问题中所问的。你想注册void SomethingHappened(int a, int b) print a + b;之类的东西。 请注意 std::any/std::variant 是 C++17,而 OP 标签是 C++11。 (所以必须使用 boost::any 代替或等效)。【参考方案2】:

您可能需要一个围绕 std::function 的包装器,例如:

template <typename T> struct Tag;

// Convert ASValue to expected type,
// Possibly throw for invalid arguments.
bool Convert(Tag<Bool>, AsValue val)  return (Boolean)val; 
int Convert(Tag<int>, AsValue val)  return (Number)val; 
// ...

struct Callback

private:
    template <std::size_t ... Is, typename Ret, typename ... Ts>
    static Ret call_impl(Ret(* func)(Ts...), std::index_sequence<Is...>)
    
        if (arr.size() != sizeof...(Is)) throw std::invalid_argument;
        return func(Convert(tag<Ts>, arr[Is])...);
    
public:
    template <typename Ret, typename ... Ts>
    Callback(Ret(* func)(Ts...)) : Call[func](ASValue::Array arr, ASValue& ret)
        
            try
            
                ret = Callback::call_impl(func, std::make_index_sequence<sizeof(...(Ts)>());
                return S_OK;
             catch (...) 
                return E_INVALIDARG;
            
        
    

    std::function<HRESULT(ASValue::Array, ASValue&)> Call;
;

std::index_sequence 是 C++14,但您可能会在 SO 上找到实现。

【讨论】:

以上是关于C++ 通用回调实现的主要内容,如果未能解决你的问题,请参考以下文章

C++的注册和回调

C++ 容器上的通用操作

在地图中存储通用回调

在 C++ 中实现通用堆栈

一个通用的Trie树,标准C++实现

一个通用的Trie树,标准C++实现