用std :: function替换函数指针实现,以便在PlayFab SDK中使用lambdas

时间:2016-08-18 21:56:01

标签: c++ c++11 lambda function-pointers std-function

我目前正在尝试将PlayFab C++ SDK添加到我的应用中。这个SDK主要用于游戏引擎Cocos2d-x,但基本上可以用于任何C ++应用程序。

它只是简单的REST,因此您将请求发送到他们的服务器并等待响应。这对于使用lambdas来说是完美的。

他们声明这个回调,当请求成功时调用它:

template<typename ResType> using ProcessApiCallback = void(*)(const ResType& result, void* userData);

不幸的是,他们没有使用std :: function,而是使用函数指针。这样就不能使用捕获变量的lambdas。

因此,我认为我可以简单地用std :: function回调替换这个函数指针回调,如下所示:

template<typename ResType> using ProcessApiCallback = std::function<void(const ResType& result, void* userData)>;

不幸的是,事情不是那么简单,因为他们使用丑陋的reinterpret_casts存储函数指针,这是一个例子(删除不必要的部分以保持简短):

void PlayFabClientAPI::LoginWithAndroidDeviceID(
    LoginWithAndroidDeviceIDRequest& request,
    ProcessApiCallback<LoginResult> callback,
    ErrorCallback errorCallback,
    void* userData
    )
{
    // here they assign the callback to the httpRequest
    httpRequest->SetResultCallback(reinterpret_cast<void*>(callback));
    httpRequest->SetErrorCallback(errorCallback);
    httpRequest->SetUserData(userData);

    PlayFabSettings::httpRequester->AddRequest(httpRequest, OnLoginWithAndroidDeviceIDResult, userData);
}

稍后,当请求成功时,他们会这样做:

if (request->GetResultCallback() != nullptr)
{
    ProcessApiCallback<LoginResult> successCallback = reinterpret_cast<ProcessApiCallback<LoginResult>>(request->GetResultCallback());
    successCallback(outResult, request->GetUserData());
}

HttpRequest类有这个字段:

void* mResultCallback;

问题是我不知道如何在HttpRequest类中存储任意std :: function指针,然后再将它们强制转换。我尝试了很多东西,包括非常丑陋的reinterpret_casting,但没有任何效果。

我愿意对他们的SDK进行任何更改。我还报告说这是一个糟糕的设计并且他们同意了,但是他们没有时间去改进它,但如果能找到一个好的解决方案,他们会接受拉取请求。

3 个答案:

答案 0 :(得分:1)

此处的关键信息项是userData指针。它作为请求的一部分提供,并传递回您的回调函数。这是一个不透明的指针,图书馆不注意,否则,除了将它转发给你的回调。

这就是你将使用的,这里。

这是一种非常常见的设计模式,其中包含用C编写的通用面向服务的库。它们的API通常采用这种方式构建:它们接受带有额外不透明指针的请求。它们存储此指针,并在请求完成时将其传递回用户提供的回调。

然后,回调使用它将任何类型的附加元数据与请求相关联。

这是一个C ++库,但他们选择为库回调实现C风格的设计模式。那是不幸的。

但是,无论如何,在您的情况下,您将动态分配您的std::function或包含您的std::function的某个类的实例,以及它需要的任何其他数据,并将指针传递给请求的动态分配结构。

当你的回调被调用时,它只需要reinterpret_cast指向动态分配类型的opaque指针,复制它的内容,delete它(当然为了避免内存泄漏),然后继续使用复制的内容作为回调操作的一部分(无论是涉及调用std::function还是其他东西,都是无关紧要的。)

鉴于这是你正在使用的C ++库,而不是C库,不幸的是他们选择实现这种C风格的不透明指针传递设计模式。当然,有更好的方法可以在C ++中实现它,但这是你必须使用的,所以你必须处理一个丑陋的reintepret_cast。没办法避免它。

答案 1 :(得分:1)

要详细说明@SamVarshavchik的答案,这里有一段代码片段,它应该处理生成userData指针和“{无状态”回调函数的大部分过程,为您提供userData

#include <memory>
#include <utility>
#include <type_traits>
#include <iostream>

template <typename T, typename... Args>
void c_callback_adaptor(Args... args, void* userData) {
    auto callable = reinterpret_cast<T*>(userData);
    (*callable)(args...);
}

template <typename Fn>
struct genCCallback_impl;
template <typename Res, typename... Args>
struct genCCallback_impl<Res(Args...)> {
    template <typename T>
    static std::pair<std::unique_ptr<std::decay_t<T>>,
                     Res (*)(Args..., void*)>
    gen(T&& callable) {
        return std::make_pair(
            std::make_unique<std::decay_t<T>>(std::forward<T>(callable)),
            c_callback_adaptor<std::decay_t<T>, Args...>);
    }
};

template <typename Fn, typename T>
auto genCCallback(T&& callable) {
    return genCCallback_impl<Fn>::gen(std::forward<T>(callable));
}

一个简单的用法示例:

extern void registerCallback(void (*callbackFn)(int, int, void*), void* userData);

void test(int n) {
    auto cCallback = genCCallback<void(int, int)>(
        [n](int x, int y) {
            std::cout << n << ' ' << x << ' ' << y << '\n';
        });
    registerCallback(cCallback.second, cCallback.first.get());
    // for demo purposes make the allocated memory permanent
    (void) cCallback.first.release();
}

(当然,在实际代码中,您需要跟踪std::unique_ptr,直到您准备取消注册回调为止。)

在回答评论时:为了分解模板在幕后所做的事情,假设示例说明lambda类的内部名称是__lambda1。然后上面的test()函数生成的代码基本上等同于:

void c_callback_adaptor_lambda1(int x, int y, void* userData) {
    auto callable = reinterpret_cast<__lambda1*>(userData);
    (*callable)(x, y);
}

class __lambda1 {
public:
    __lambda1(int n) { ... }
    void operator()(int x, int y) const { std::cout << ...; }
    ...
private: ...
};

void test(int n) {
    auto cCallback = std::make_pair(
        std::make_unique<__lambda1>(n),
        c_callback_adaptor_lambda1);
    registerCallback(cCallback.second, cCallback.first.get());
    (void) cCallback.first.release();
}

答案 2 :(得分:0)

您已链接的PlayFab Cocos2dxSDK已升级为支持lambda函数而无需修改SDK。

例如:

PlayFabClientAPI::LoginWithEmailAddress(request,
    [](const LoginResult& result, void* customData) { /* your login-success lambda here */ },
    [](const PlayFabError& error, void* customData) { /* your error lambda here */ },
    nullptr);