如何将类成员函数作为回调传递?

新手上路,请多包涵

我正在使用一个 API,它要求我将函数指针作为回调传递。我正在尝试在我的课堂上使用这个 API,但我遇到了编译错误。

这是我从构造函数中所做的:

 m_cRedundencyManager->Init(this->RedundencyManagerCallBack);

这不会编译 - 我收到以下错误:

错误 8 错误 C3867:’CLoggersInfra::RedundencyManagerCallBack’:函数调用缺少参数列表;使用 ‘&CLoggersInfra::RedundencyManagerCallBack’ 创建指向成员的指针

我尝试了使用 &CLoggersInfra::RedundencyManagerCallBack 的建议对我不起作用。

对此有任何建议/解释吗?

我正在使用VS2008。

谢谢!!

原文由 ofer 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.1k
2 个回答

这不起作用,因为成员函数指针不能像普通函数指针那样处理,因为它需要一个“this”对象参数。

相反,您可以按如下方式传递静态成员函数,这在这方面类似于普通的非成员函数:

 m_cRedundencyManager->Init(&CLoggersInfra::Callback, this);

函数可以定义如下

static void Callback(int other_arg, void * this_pointer) {
    CLoggersInfra * self = static_cast<CLoggersInfra*>(this_pointer);
    self->RedundencyManagerCallBack(other_arg);
}

原文由 Johannes Schaub - litb 发布,翻译遵循 CC BY-SA 4.0 许可协议

将 C 风格的回调函数与 C++ 类实例连接起来仍然很困难。我想改写一下原来的问题:

  • 您正在使用的某些库需要从该库回调的 C 样式函数。更改库 API 是不可能的,因为它不是 您的 API。

  • 您希望在您自己的 C++ 代码中的成员方法中处理回调

由于您没有(确切地)提到您要处理的回调,我将举一个使用 GLFW 回调进行键输入 的示例。 (附带说明:我知道 GLFW 提供了一些其他机制来将用户数据附加到他们的 API,但这不是这里的主题。)

我不知道任何不包括使用某种静态对象的解决方案。让我们看看我们的选择:

简单方法:使用 C 风格的全局对象

正如我们总是在类和实例中思考的那样,我们有时会忘记在 C++ 中我们仍然拥有整个 C 库。所以有时这个非常简单的解决方案不会出现在脑海中。

假设我们有一个类 Presentation 应该处理键盘输入。这可能看起来像这样:

 struct KeyInput {
    int pressedKey;
} KeyInputGlobal;

void globalKeyHandler(GLFWwindow* window, int key, int scancode, int action, int mods) {
    KeyInputGlobal.pressedKey = key;
}

int Presentation::getCurrentKey()
{
    return KeyInputGlobal.pressedKey;
}

void Presentation::initGLFW()
{
    glfwInit();
    glfwSetKeyCallback(window, globalKeyHandler);
}

我们有一个全局对象 KeyInputGlobal 应该接收按下的键。函数 globalKeyHandler 具有 GLFW 库所需的 C 样式 API 签名,以便能够调用我们的代码。它在我们的成员方法 initGLFW 上被激活。如果在我们的代码中的任何地方我们对当前按下的键感兴趣,我们可以调用另一个成员方法 Presentation::getCurrentKey

这种方法有什么问题?

也许一切都好。完全取决于您的用例。也许您完全可以在应用程序代码中的某处读取最后按下的键。您不在乎错过按键事件。简单的方法就是您所需要的。

概括一下:如果您能够在 C 风格的代码中完全处理回调,计算一些结果并将其存储在一个全局对象中,以便稍后从代码的其他部分读取,那么使用这种简单的方法确实是有意义的。从好的方面来说:这很容易理解。不足之处?感觉有点像作弊,因为你并没有真正处理你的 C++ 代码中的回调,你只是使用了结果。如果您将回调视为一个事件并希望在您的成员方法中正确处理每个事件,则此方法是不够的。

另一种简单的方法:使用 C++ 静态对象

我想我们中的许多人已经这样做了。我当然有。思考:等等,我们有一个 C++ 的全局概念,即使用 static 。但是我们可以在这里保持简短的讨论:它可能比使用前面示例中的 C 风格更具 C++ 风格,但问题是相同的 - 我们仍然有全局变量,很难将其与非静态的常规成员方法结合在一起.为了完整起见,在我们的类声明中它看起来像这样:

 class Presentation
{
public:
    struct KeyInput {
        int pressedKey;
    };
    static KeyInput KeyInputGlobal;

    static void globalKeyHandler(GLFWwindow* window, int key, int scancode, int action, int mods) {
        KeyInputGlobal.pressedKey = key;
    }
    int getCurrentKey()
    {
        return KeyInputGlobal.pressedKey;
    }
...
}

激活我们的回调看起来是一样的,但我们还必须定义接收在我们的实现中按下的键的静态结构:

 void Presentation::initGLFW()
{
    glfwInit();
    glfwSetKeyCallback(window, globalKeyHandler);
}
//static
Presentation::KeyInput Presentation::KeyInputGlobal;

您可能倾向于从我们的回调方法 globalKeyHandler 中删除 static 关键字:编译器会立即告诉您,您不能再将其传递给 glfwSetKeyCallback() 中的 GLFW。现在,如果我们只能以某种方式将静态方法与常规方法连接起来……

使用静态和 lambda 的 C++11 事件驱动方法

我能找到的最佳解决方案如下。它可以工作并且有点优雅,但我仍然认为它并不完美。让我们看一下并讨论:

 void Presentation::initGLFW()
{
    glfwInit();
    static auto callback_static = [this](
           GLFWwindow* window, int key, int scancode, int action, int mods) {
        // because we have a this pointer we are now able to call a non-static member method:
        key_callbackMember(window, key, scancode, action, mods);
    };
    glfwSetKeyCallback(window,
    [](GLFWwindow* window, int key, int scancode, int action, int mods)
      {
        // only static methods can be called here as we cannot change glfw function parameter list to include instance pointer
        callback_static(window, key, scancode, action, mods);
      }
    );
}

void Presentation::key_callbackMember(GLFWwindow* window, int key, int scancode, int action, int mods)
{
    // we can now call anywhere in our code to process the key input:
    app->handleInput(key, action);
}

_callbackstatic 的定义是我们将静态对象与实例数据连接起来,在这种情况下, 是我们的 Presentation 类的一个实例。您可以阅读定义如下:如果在此定义之后的任何时间 _调用callbackstatic ,所有参数都将传递给刚刚使用的 Presentation 实例调用的成员方法 _keycallbackMember 。这个定义与 GLFW 库没有任何关系——它只是为下一步做准备。

我们现在使用第二个 lambda 向 glfwSetKeyCallback() 中的库注册我们的回调。同样,如果 _callbackstatic 没有被定义为 _静态_,我们不能在这里将它传递给 GLFW。

当 GLFW 调用我们的代码时,这是在所有初始化之后在运行时发生的情况:

  1. GLFW 识别一个关键事件并调用我们的静态对象 _callbackstatic
  2. _callbackstatic 可以访问 Presentation 类的实例并调用它的实例方法 _keycallbackMember
  3. 现在我们处于“对象世界”中,我们可以在其他地方处理关键事件。在这种情况下,我们在某个任意对象 app 上调用方法 handleInput ,该对象已在我们代码的其他地方设置。

好处:我们已经实现了我们想要的,无需在初始化方法 initGLFW 之外定义全局对象。不需要 C 风格的全局变量。

坏处:不要仅仅因为所有东西都整齐地包装在一种方法中而被愚弄。我们 仍然 有静态对象。与它们一起出现的所有问题都是全局对象所具有的。例如,多次调用我们的初始化方法(使用不同的 Presentation 实例)可能不会达到您想要的效果。

概括

可以将现有库的 C 样式回调连接到您自己代码中的类实例。您可以尝试通过在代码的成员方法中定义必要的对象来最小化管理代码。但是对于每个回调,您仍然需要一个静态对象。如果你想用 C 风格的回调连接你的 C++ 代码的几个实例,准备好引入比上面的例子更复杂的静态对象管理。

希望这可以帮助某人。快乐编码。

原文由 Clemens Fehr 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题