【问题标题】:C++ generic callback implementationC++ 通用回调实现
【发布时间】: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/…

标签: c++ c++11 templates flash boost


【解决方案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 上找到实现。

【讨论】:

    猜你喜欢
    • 2021-05-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-12
    相关资源
    最近更新 更多