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

发布于 2024-07-11 03:54:30 字数 521 浏览 13 评论 0 原文

我使用的 API 要求我传递函数指针作为回调。 我正在尝试在我的类中使用此 API,但出现编译错误。

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

m_cRedundencyManager->Init(this->RedundencyManagerCallBack);

这无法编译 - 我收到以下错误:

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

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

对此有什么建议/解释吗?

我用的是VS2008。

谢谢!!

I'm using an API that requires me to pass a function pointer as a callback. I'm trying to use this API from my class but I'm getting compilation errors.

Here is what I did from my constructor:

m_cRedundencyManager->Init(this->RedundencyManagerCallBack);

This doesn't compile - I get the following error:

Error 8 error C3867: 'CLoggersInfra::RedundencyManagerCallBack': function call missing argument list; use '&CLoggersInfra::RedundencyManagerCallBack' to create a pointer to member

I tried the suggestion to use &CLoggersInfra::RedundencyManagerCallBack - didn't work for me.

Any suggestions/explanation for this??

I'm using VS2008.

Thanks!!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(14

饮湿 2024-07-18 03:54:30

这是一个简单的问题,但答案却出奇地复杂。 简而言之,您可以使用 std::bind1stboost::bind 执行您想要执行的操作。 较长的答案如下。

编译器正确地建议您使用 &CLoggersInfra::RedundencyManagerCallBack。 首先,如果 RedundencyManagerCallBack 是成员函数,则该函数本身不属于 CLoggersInfra 类的任何特定实例。 它属于类本身。 如果您以前曾经调用过静态类函数,您可能已经注意到您使用了相同的 SomeClass::SomeMemberFunction 语法。 由于函数本身是“静态”的,因为它属于类而不是特定实例,因此您可以使用相同的语法。 '&' 是必要的,因为从技术上讲,您不直接传递函数——函数不是 C++ 中的真实对象。 相反,从技术上讲,您传递的是函数的内存地址,即指向函数指令在内存中开始位置的指针。 结果是相同的,您实际上是“传递函数”作为参数。

但这只是本例中问题的一半。 正如我所说,RedundencyManagerCallBack 函数不“属于”任何特定实例。 但听起来您想将其作为回调传递,并考虑到特定的实例。 要了解如何执行此操作,您需要了解成员函数的真正含义:带有额外隐藏参数的常规未在任何类中定义的函数。

例如:

class A {
public:
    A() : data(0) {}
    void foo(int addToData) { this->data += addToData; }

    int data;
};

...

A an_a_object;
an_a_object.foo(5);
A::foo(&an_a_object, 5); // This is the same as the line above!
std::cout << an_a_object.data; // Prints 10!

A::foo 有多少个参数? 通常我们会说 1。但在幕后, foo 实际上需要 2。看看 A::foo 的定义,它需要 A 的特定实例才能使“this”指针有意义(编译器需要知道“这是)。 通常指定“this”的方式是通过语法 MyObject.MyMemberFunction()。 但这只是将 MyObject 的地址作为第一个参数传递给 MyMemberFunction 的语法糖。 类似地,当我们在类定义中声明成员函数时,我们不会将“this”放入参数列表中,但这只是语言设计者为节省打字而提供的礼物。 相反,您必须指定成员函数是静态的,才能选择不自动获取额外的“this”参数。 如果 C++ 编译器将上面的示例翻译为 C 代码(原始 C++ 编译器实际上是这样工作的),它可能会写出如下内容:

struct A {
    int data;
};

void a_init(A* to_init)
{
    to_init->data = 0;
}

void a_foo(A* this, int addToData)
{ 
    this->data += addToData;
}

...

A an_a_object;
a_init(0); // Before constructor call was implicit
a_foo(&an_a_object, 5); // Used to be an_a_object.foo(5);

回到您的示例,现在有一个明显的问题。 “Init”需要一个指向带有一个参数的函数的指针。 但是 &CLoggersInfra::RedundencyManagerCallBack 是一个指向带有两个参数的函数的指针,它是普通参数和秘密的“this”参数。 这就是为什么你仍然会收到编译器错误(附带说明:如果你曾经使用过 Python,这种混乱就是为什么所有成员函数都需要“self”参数)。

处理此问题的详细方法是创建一个特殊对象,该对象保存指向所需实例的指针,并具有一个名为“run”或“execute”(或重载“()”运算符)的成员函数,该函数接受参数对于成员函数,只需在存储的实例上使用这些参数调用成员函数即可。 但这需要您更改“Init”以获取特殊对象而不是原始函数指针,而且听起来 Init 是其他人的代码。 每次出现这个问题时都创建一个特殊的类会导致代码膨胀。

现在,终于有了一个好的解决方案,boost::bindboost::function,您可以在这里找到每个解决方案的文档:

boost::bind 文档,
boost::function 文档

boost ::bind 将允许您获取一个函数以及该函数的一个参数,并创建一个新函数,其中该参数被“锁定”到位。 因此,如果我有一个将两个整数相加的函数,我可以使用 boost::bind 来创建一个新函数,其中一个参数被锁定为 5。这个新函数将仅采用一个整数参数,并且总是会专门为其添加 5。 使用这种技术,您可以将隐藏的“this”参数“锁定”为特定的类实例,并生成一个仅采用一个参数的新函数,就像您想要的那样(请注意,隐藏参数始终是 第一个参数,普通参数按顺序排列在它之后)。 查看 boost::bind 文档中的示例,他们甚至专门讨论了将其用于成员函数。 从技术上讲,您也可以使用一个名为 [std::bind1st][3] 的标准函数,但 boost::bind 更为通用。

当然,还有一个问题。 boost::bind 将为您提供一个不错的 boost::function ,但从技术上讲,这仍然不是像 Init 可能想要的原始函数指针。 值得庆幸的是,boost 提供了一种将 boost::function 转换为原始指针的方法,如 StackOverflow

如果这看起来非常困难,请不要担心——您的问题涉及 C++ 的几个阴暗角落,并且一旦您学会了 boost::bind 就会非常有用。

C++11 更新:您现在可以使用捕获“this”的 lambda 函数,而不是 boost::bind。 这基本上是让编译器为您生成相同的东西。

This is a simple question but the answer is surprisingly complex. The short answer is you can do what you're trying to do with std::bind1st or boost::bind. The longer answer is below.

The compiler is correct to suggest you use &CLoggersInfra::RedundencyManagerCallBack. First, if RedundencyManagerCallBack is a member function, the function itself doesn't belong to any particular instance of the class CLoggersInfra. It belongs to the class itself. If you've ever called a static class function before, you may have noticed you use the same SomeClass::SomeMemberFunction syntax. Since the function itself is 'static' in the sense that it belongs to the class rather than a particular instance, you use the same syntax. The '&' is necessary because technically speaking you don't pass functions directly -- functions are not real objects in C++. Instead you're technically passing the memory address for the function, that is, a pointer to where the function's instructions begin in memory. The consequence is the same though, you're effectively 'passing a function' as a parameter.

But that's only half the problem in this instance. As I said, RedundencyManagerCallBack the function doesn't 'belong' to any particular instance. But it sounds like you want to pass it as a callback with a particular instance in mind. To understand how to do this you need to understand what member functions really are: regular not-defined-in-any-class functions with an extra hidden parameter.

For example:

class A {
public:
    A() : data(0) {}
    void foo(int addToData) { this->data += addToData; }

    int data;
};

...

A an_a_object;
an_a_object.foo(5);
A::foo(&an_a_object, 5); // This is the same as the line above!
std::cout << an_a_object.data; // Prints 10!

How many parameters does A::foo take? Normally we would say 1. But under the hood, foo really takes 2. Looking at A::foo's definition, it needs a specific instance of A in order for the 'this' pointer to be meaningful (the compiler needs to know what 'this' is). The way you usually specify what you want 'this' to be is through the syntax MyObject.MyMemberFunction(). But this is just syntactic sugar for passing the address of MyObject as the first parameter to MyMemberFunction. Similarly, when we declare member functions inside class definitions we don't put 'this' in the parameter list, but this is just a gift from the language designers to save typing. Instead you have to specify that a member function is static to opt out of it automatically getting the extra 'this' parameter. If the C++ compiler translated the above example to C code (the original C++ compiler actually worked that way), it would probably write something like this:

struct A {
    int data;
};

void a_init(A* to_init)
{
    to_init->data = 0;
}

void a_foo(A* this, int addToData)
{ 
    this->data += addToData;
}

...

A an_a_object;
a_init(0); // Before constructor call was implicit
a_foo(&an_a_object, 5); // Used to be an_a_object.foo(5);

Returning to your example, there is now an obvious problem. 'Init' wants a pointer to a function that takes one parameter. But &CLoggersInfra::RedundencyManagerCallBack is a pointer to a function that takes two parameters, it's normal parameter and the secret 'this' parameter. That's why you're still getting a compiler error (as a side note: If you've ever used Python, this kind of confusion is why a 'self' parameter is required for all member functions).

The verbose way to handle this is to create a special object that holds a pointer to the instance you want and has a member function called something like 'run' or 'execute' (or overloads the '()' operator) that takes the parameters for the member function, and simply calls the member function with those parameters on the stored instance. But this would require you to change 'Init' to take your special object rather than a raw function pointer, and it sounds like Init is someone else's code. And making a special class for every time this problem comes up will lead to code bloat.

So now, finally, the good solution, boost::bind and boost::function, the documentation for each you can find here:

boost::bind docs,
boost::function docs

boost::bind will let you take a function, and a parameter to that function, and make a new function where that parameter is 'locked' in place. So if I have a function that adds two integers, I can use boost::bind to make a new function where one of the parameters is locked to say 5. This new function will only take one integer parameter, and will always add 5 specifically to it. Using this technique, you can 'lock in' the hidden 'this' parameter to be a particular class instance, and generate a new function that only takes one parameter, just like you want (note that the hidden parameter is always the first parameter, and the normal parameters come in order after it). Look at the boost::bind docs for examples, they even specifically discuss using it for member functions. Technically there is a standard function called [std::bind1st][3] that you could use as well, but boost::bind is more general.

Of course, there's just one more catch. boost::bind will make a nice boost::function for you, but this is still technically not a raw function pointer like Init probably wants. Thankfully, boost provides a way to convert boost::function's to raw pointers, as documented on StackOverflow here. How it implements this is beyond the scope of this answer, though it's interesting too.

Don't worry if this seems ludicrously hard -- your question intersects several of C++'s darker corners, and boost::bind is incredibly useful once you learn it.

C++11 update: Instead of boost::bind you can now use a lambda function that captures 'this'. This is basically having the compiler generate the same thing for you.

ま柒月 2024-07-18 03:54:30

这是行不通的,因为成员函数指针不能像普通函数指针一样处理,因为它需要一个“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);
}

That doesn't work because a member function pointer cannot be handled like a normal function pointer, because it expects a "this" object argument.

Instead you can pass a static member function as follows, which are like normal non-member functions in this regard:

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

The function can be defined as follows

static void Callback(int other_arg, void * this_pointer) {
    CLoggersInfra * self = static_cast<CLoggersInfra*>(this_pointer);
    self->RedundencyManagerCallBack(other_arg);
}
说谎友 2024-07-18 03:54:30

这个答案是对上面评论的回复,不适用于 VisualStudio 2008,但应首选更新的编译器。


同时,您不必再使用 void 指针,也不需要 boost,因为 std::bind< code>std::function 可用。 一个优点(与 void 指针相比)是类型安全,因为返回类型和参数是使用 std::function 显式声明的:

// std::function<return_type(list of argument_type(s))>
void Init(std::function<void(void)> f);

然后您可以使用以下命令创建函数指针std::bind 并将其传递给 Init:

auto cLoggersInfraInstance = CLoggersInfra();
auto callback = std::bind(&CLoggersInfra::RedundencyManagerCallBack, cLoggersInfraInstance);
Init(callback);

完整示例std::bind 与成员、静态成员和非成员函数一起使用:

#include <functional>
#include <iostream>
#include <string>

class RedundencyManager // incl. Typo ;-)
{
public:
    // std::function<return_type(list of argument_type(s))>
    std::string Init(std::function<std::string(void)> f) 
    {
        return f();
    }
};

class CLoggersInfra
{
private:
    std::string member = "Hello from non static member callback!";

public:
    static std::string RedundencyManagerCallBack()
    {
        return "Hello from static member callback!";
    }

    std::string NonStaticRedundencyManagerCallBack()
    {
        return member;
    }
};

std::string NonMemberCallBack()
{
    return "Hello from non member function!";
}

int main()
{
    auto instance = RedundencyManager();

    auto callback1 = std::bind(&NonMemberCallBack);
    std::cout << instance.Init(callback1) << "\n";

    // Similar to non member function.
    auto callback2 = std::bind(&CLoggersInfra::RedundencyManagerCallBack);
    std::cout << instance.Init(callback2) << "\n";

    // Class instance is passed to std::bind as second argument.
    // (heed that I call the constructor of CLoggersInfra)
    auto callback3 = std::bind(&CLoggersInfra::NonStaticRedundencyManagerCallBack,
                               CLoggersInfra()); 
    std::cout << instance.Init(callback3) << "\n";
}

可能的输出:

Hello from non member function!
Hello from static member callback!
Hello from non static member callback!

此外使用 std::placeholders 您可以动态地将参数传递给回调(例如,这可以使用 return f("MyString");如果 f 有一个字符串参数,则在 Init 中执行

This answer is a reply to a comment above and does not work with VisualStudio 2008 but should be preferred with more recent compilers.


Meanwhile you don't have to use a void pointer anymore and there is also no need for boost since std::bind and std::function are available. One advantage (in comparison to void pointers) is type safety since the return type and the arguments are explicitly stated using std::function:

// std::function<return_type(list of argument_type(s))>
void Init(std::function<void(void)> f);

Then you can create the function pointer with std::bind and pass it to Init:

auto cLoggersInfraInstance = CLoggersInfra();
auto callback = std::bind(&CLoggersInfra::RedundencyManagerCallBack, cLoggersInfraInstance);
Init(callback);

Complete example for using std::bind with member, static members and non member functions:

#include <functional>
#include <iostream>
#include <string>

class RedundencyManager // incl. Typo ;-)
{
public:
    // std::function<return_type(list of argument_type(s))>
    std::string Init(std::function<std::string(void)> f) 
    {
        return f();
    }
};

class CLoggersInfra
{
private:
    std::string member = "Hello from non static member callback!";

public:
    static std::string RedundencyManagerCallBack()
    {
        return "Hello from static member callback!";
    }

    std::string NonStaticRedundencyManagerCallBack()
    {
        return member;
    }
};

std::string NonMemberCallBack()
{
    return "Hello from non member function!";
}

int main()
{
    auto instance = RedundencyManager();

    auto callback1 = std::bind(&NonMemberCallBack);
    std::cout << instance.Init(callback1) << "\n";

    // Similar to non member function.
    auto callback2 = std::bind(&CLoggersInfra::RedundencyManagerCallBack);
    std::cout << instance.Init(callback2) << "\n";

    // Class instance is passed to std::bind as second argument.
    // (heed that I call the constructor of CLoggersInfra)
    auto callback3 = std::bind(&CLoggersInfra::NonStaticRedundencyManagerCallBack,
                               CLoggersInfra()); 
    std::cout << instance.Init(callback3) << "\n";
}

Possible output:

Hello from non member function!
Hello from static member callback!
Hello from non static member callback!

Furthermore using std::placeholders you can dynamically pass arguments to the callback (e.g. this enables the usage of return f("MyString"); in Init if f has a string parameter).

落叶缤纷 2024-07-18 03:54:30

将 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++ 概念,即使用静态。 但我们可以在这里简短地讨论一下:它可能比使用前面示例中的 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。 现在,如果我们只能以某种方式将静态方法与常规方法连接起来...

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

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

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);
}

callback_static 的定义是我们将静态对象与实例数据连接起来的地方,在本例中 this 是我们Presentation<的一个实例/em> 类。 您可以如下阅读该定义:如果在此定义之后的任何时间调用callback_static,则所有参数都将传递给在Presentationkey_callbackMember em> 刚刚使用的实例。 这个定义与 GLFW 库无关——它只是为下一步做准备。

现在,我们使用第二个 lambda 在 glfwSetKeyCallback() 中向库注册我们的回调。 同样,如果callback_static没有被定义为static,我们就无法将它传递给GLFW。

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

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

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

坏处:不要仅仅因为所有内容都整齐地打包到一种方法中而被愚弄。 我们仍然有静态对象。 全局对象所面临的所有问题也随之而来。 例如,多次调用我们的初始化方法(使用不同的Presentation实例)可能不会达到您想要的效果。

摘要

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

希望这对某人有帮助。 快乐编码。

It is still difficult to connect C style callback functions with C++ class instances. I want to kind of rephrase the original question:

  • Some library you are using requires a C style function to be called back from that library. Changing the library API is out of the question since it is not your API.

  • You want the callback to be handled in your own C++ code in member methods

As you did not mention (exactly) what callback you want to handle I will give an example using GLFW callbacks for key input. (On a side note: I know GLFW offers some other mechanism to attach user data to their API, but that is not the topic here.)

I don't know any solution to this problem that doesn't include usage of some kind of static object. Let's look at our options:

Simple approach: Use C style global objects

As we always think in classes and instances we sometimes forget that in C++ we still have the whole arsenal of C at our hands. So sometimes this very simple solution does not come to mind.

Let's assume we have a class Presentation that should handle keyboard input. This could look like this:

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);
}

We have a global Object KeyInputGlobal that should receive the key pressed. The function globalKeyHandler has exactly the C style API signature needed by the GLFW library to be able to call our code. It is activated on our member method initGLFW. If anywhere in our code we are interested in the currently pressed key we can just call the other member method Presentation::getCurrentKey

What is wrong with this approach?

Maybe it is all fine. Depends entirely on your use case. Maybe you are totally fine to just read the last pressed key somwhere in your application code. You don't care to have missed key pressed events. The simple approach is all you need.

To generalize: If you are able to fully process the callback in C style code, calculate some result and store it in a global object to be read later from other parts of your code, then it may indeed make sense to use this simple approach. On the plus side: It is very simple to understand. The downside? It feels a little bit like cheating, because you didn't really process the callback in your C++ code, you just used the results. If you think of the callback as an event and want each event to be properly processed in your member methods this approch won't be enough.

Another simple approach: Use C++ static objects

I guess many of us have already done this. Certainly I have. Thinking: Wait, we have a C++ concept of globals, that is using static. But we can keep the discussion short here: It may be more C++ style than using the C style from previous example, but the problems are the same - we still have globals, that are hard to bring together with non-static, regular member methods. For completeness, it would look like this in our class declaration:

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;
    }
...
}

Activating our callback would look the same, but we also have to define the static struct that receives the key pressed in our implementation:

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

You might be inclined to just remove the static keyword from our callback method globalKeyHandler: The compiler will immediately tell you that you can no longer pass this to GLFW in glfwSetKeyCallback(). Now, if we only could connect static methods with regular methods somehow...

C++11 Event driven approach with statics and lambdas

The best solution I could find out is the following. It works and is somewhat elegant, but I still do not consider it perfect. Let's look at it and discuss:

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);
}

The definition of callback_static is where we connect a static object with instance data, in this case this is an instance of our Presentation class. You can read the definition as follows: If callback_static is called anytime after this definition, all parameters will be passed to the member method key_callbackMember called at the Presentation instance just used. This definition has nothing to do with the GLFW library yet - it is just the preparation for the next step.

We now use a second lambda to register our callback with the library in glfwSetKeyCallback(). Again, if callback_static would not have been defined as static we could not pass it to GLFW here.

This is what happens at runtime after all the initializations, when GLFW calls our code:

  1. GLFW recognizes a key event and calls our static object callback_static
  2. callback_static has access to an instance of Presentation class and calls it's instance method key_callbackMember
  3. Now that we are in 'object world' we can process the key event somewhere else. In this case we call the method handleInput on some arbitrary object app, that has been setup somewhere else in our code.

The good: We have achieved what we wanted with no need to define global objects outside our initialization method initGLFW. No need for C style globals.

The bad: Don't be fooled just because everything is neatly packed into one method. We still have static objects. And with them all the problems global objects have. E.g. multiple calls to our initialization method (with different instances of Presentation) would probably not have the effect you intended.

Summary

It is possible to connect C style callbacks of existing libraries to instances of classes in your own code. You can try to minimize houeskeeping code by defining the necessary objects in member methods of your code. But you still need one static object for each callback. If you want to connect several instances of your C++ code with a C style callback be prepared to introduce a more complicated management of your static objects than in the example above.

Hope this helps someone. Happy coding.

青衫负雪 2024-07-18 03:54:30

在 c++14 或更高版本中,有一种令人惊讶的简单方法:

auto callback = [this](){ this->methodCB(); };
subscribeToEvent(callback);

**假设 subscribeToEvent 获取 std::function

There's a surprisingly simple way to do so in c++14 or above:

auto callback = [this](){ this->methodCB(); };
subscribeToEvent(callback);

**assuming subscribeToEvent gets std::function<void()>

浪漫之都 2024-07-18 03:54:30

Init 采用什么参数? 新的错误消息是什么?

C++ 中的方法指针有点难以使用。 除了方法指针本身之外,您还需要提供一个实例指针(在您的情况下为 this)。 也许 Init 希望它作为一个单独的参数?

What argument does Init take? What is the new error message?

Method pointers in C++ are a bit difficult to use. Besides the method pointer itself, you also need to provide an instance pointer (in your case this). Maybe Init expects it as a separate argument?

久而酒知 2024-07-18 03:54:30

指向类成员函数的指针与指向函数的指针不同。 类成员采用隐式额外参数(this 指针),并使用不同的调用约定。

如果您的 API 需要非成员回调函数,那么您必须将其传递给它。

A pointer to a class member function is not the same as a pointer to a function. A class member takes an implicit extra argument (the this pointer), and uses a different calling convention.

If your API expects a nonmember callback function, that's what you have to pass to it.

傲性难收 2024-07-18 03:54:30

m_cRedundencyManager 能够使用成员函数吗? 大多数回调都设置为使用常规函数或静态成员函数。 请查看 C++ FAQ Lite 中的此页面了解更多信息。

更新:您提供的函数声明显示 m_cRedundencyManager 需要以下形式的函数:void yourCallbackFunction(int, void *)。 因此,在这种情况下,成员函数作为回调是不可接受的。 静态成员函数可能可以工作,但如果这在您的情况下是不可接受的,则以下代码也可以工作。 请注意,它使用了 void * 的邪恶转换。


// in your CLoggersInfra constructor:
m_cRedundencyManager->Init(myRedundencyManagerCallBackHandler, this);

// in your CLoggersInfra header:
void myRedundencyManagerCallBackHandler(int i, void * CLoggersInfraPtr);

// in your CLoggersInfra source file:
void myRedundencyManagerCallBackHandler(int i, void * CLoggersInfraPtr)
{
    ((CLoggersInfra *)CLoggersInfraPtr)->RedundencyManagerCallBack(i);
}

Is m_cRedundencyManager able to use member functions? Most callbacks are set up to use regular functions or static member functions. Take a look at this page at C++ FAQ Lite for more information.

Update: The function declaration you provided shows that m_cRedundencyManager is expecting a function of the form: void yourCallbackFunction(int, void *). Member functions are therefore unacceptable as callbacks in this case. A static member function may work, but if that is unacceptable in your case, the following code would also work. Note that it uses an evil cast from void *.


// in your CLoggersInfra constructor:
m_cRedundencyManager->Init(myRedundencyManagerCallBackHandler, this);

// in your CLoggersInfra header:
void myRedundencyManagerCallBackHandler(int i, void * CLoggersInfraPtr);

// in your CLoggersInfra source file:
void myRedundencyManagerCallBackHandler(int i, void * CLoggersInfraPtr)
{
    ((CLoggersInfra *)CLoggersInfraPtr)->RedundencyManagerCallBack(i);
}
愿得七秒忆 2024-07-18 03:54:30

死灵术。
我认为迄今为止的答案还有些不清楚。

让我们举个例子:

假设你有一个像素数组(ARGB int8_t 值的数组),

// A RGB image
int8_t* pixels = new int8_t[1024*768*4];

现在你想生成一个 PNG。
为此,您调用函数 toJpeg

bool ok = toJpeg(writeByte, pixels, width, height);

,其中 writeByte 是回调函数

void writeByte(unsigned char oneByte)
{
    fputc(oneByte, output);
}

这里的问题:FILE* 输出必须是全局变量。
如果您处于多线程环境(例如 http 服务器)中,则非常糟糕。

因此,您需要某种方法使输出成为非全局变量,同时保留回调签名。

立即想到的解决方案是闭包,我们可以使用带有成员函数的类来模拟它。

class BadIdea {
private:
    FILE* m_stream;
public:
    BadIdea(FILE* stream)  {
        this->m_stream = stream;
    }

    void writeByte(unsigned char oneByte){
            fputc(oneByte, this->m_stream);
    }

};

然后做

FILE *fp = fopen(filename, "wb");
BadIdea* foobar = new BadIdea(fp);

bool ok = TooJpeg::writeJpeg(foobar->writeByte, image, width, height);
delete foobar;
fflush(fp);
fclose(fp);

然而,事与愿违,这行不通。

原因是,C++ 成员函数的实现有点像 C# 扩展函数。

所以

class/struct BadIdea
{
    FILE* m_stream;
}

当你想调用

static class BadIdeaExtensions
{
    public static writeByte(this BadIdea instance, unsigned char oneByte)
    {
         fputc(oneByte, instance->m_stream);
    }

}

writeByte 时,你不仅需要传递 writeByte 的地址,还需要传递 BadIdea 实例的地址。

因此,当您有 writeByte 过程的 typedef,并且它看起来像这样

typedef void (*WRITE_ONE_BYTE)(unsigned char);

并且您有一个看起来像这样的 writeJpeg 签名时,从

bool writeJpeg(WRITE_ONE_BYTE output, uint8_t* pixels, uint32_t 
 width, uint32_t height))
    { ... }

根本上不可能将两地址成员函数传递给一地址函数指针(不修改 writeJpeg),没有办法解决这个问题。

在 C++ 中你可以做的下一个最好的事情是使用 lambda 函数:

FILE *fp = fopen(filename, "wb");
auto lambda = [fp](unsigned char oneByte) { fputc(oneByte, fp);  };
bool ok = TooJpeg::writeJpeg(lambda, image, width, height);

但是,因为 lambda 没有做任何不同的事情,而不是将实例传递给隐藏类(例如“BadIdea”类),所以你需要修改writeJpeg 的签名。

与手动类相比,lambda 的优点是您只需将一个 typedef 更改

typedef void (*WRITE_ONE_BYTE)(unsigned char);

using WRITE_ONE_BYTE = std::function<void(unsigned char)>; 

然后您可以保留其他所有内容不变。

您也可以使用 std::bind

auto f = std::bind(&BadIdea::writeByte, &foobar);

但这在幕后只是创建了一个 lambda 函数,然后该函数还需要更改 typedef。

所以不,没有办法将成员函数传递给需要静态函数指针的方法。

但 lambda 是一种简单的方法,只要您能够控制源代码。
否则,你就不走运了。
你对 C++ 无能为力。

注意:
std::function 需要 #include

但是,由于 C++ 也允许您使用 C,因此您可以使用 libffcall 用纯 C 语言编写,如果您不介意链接依赖项。

从 GNU 下载 libffcall(至少在 ubuntu 上,不要使用发行版提供的软件包 - 它已损坏),解压。

./configure
make
make install

gcc main.c -l:libffcall.a -o ma

main.c:

#include <callback.h>

// this is the closure function to be allocated 
void function (void* data, va_alist alist)
{
     int abc = va_arg_int(alist);

     printf("data: %08p\n", data); // hex 0x14 = 20
     printf("abc: %d\n", abc);

     // va_start_type(alist[, return_type]);
     // arg = va_arg_type(alist[, arg_type]);
     // va_return_type(alist[[, return_type], return_value]);

    // va_start_int(alist);
    // int r = 666;
    // va_return_int(alist, r);
}



int main(int argc, char* argv[])
{
    int in1 = 10;

    void * data = (void*) 20;
    void(*incrementer1)(int abc) = (void(*)()) alloc_callback(&function, data);
    // void(*incrementer1)() can have unlimited arguments, e.g. incrementer1(123,456);
    // void(*incrementer1)(int abc) starts to throw errors...
    incrementer1(123);
    // free_callback(callback);
    return EXIT_SUCCESS;
}

如果您使用 CMake,请在 add_executable 之后添加链接器库

add_library(libffcall STATIC IMPORTED)
set_target_properties(libffcall PROPERTIES
        IMPORTED_LOCATION /usr/local/lib/libffcall.a)
target_link_libraries(BitmapLion libffcall)

,或者您可以动态链接 libffcall

target_link_libraries(BitmapLion ffcall)

注意:
您可能想要包含 libffcall 标头和库,或者使用 libffcall 的内容创建一个 cmake 项目。

Necromancing.
I think the answers to date are a little unclear.

Let's make an example:

Supposed you have an array of pixels (array of ARGB int8_t values)

// A RGB image
int8_t* pixels = new int8_t[1024*768*4];

Now you want to generate a PNG.
To do so, you call the function toJpeg

bool ok = toJpeg(writeByte, pixels, width, height);

where writeByte is a callback-function

void writeByte(unsigned char oneByte)
{
    fputc(oneByte, output);
}

The problem here: FILE* output has to be a global variable.
Very bad if you're in a multithreaded environment (e.g. a http-server).

So you need some way to make output a non-global variable, while retaining the callback signature.

The immediate solution that springs into mind is a closure, which we can emulate using a class with a member function.

class BadIdea {
private:
    FILE* m_stream;
public:
    BadIdea(FILE* stream)  {
        this->m_stream = stream;
    }

    void writeByte(unsigned char oneByte){
            fputc(oneByte, this->m_stream);
    }

};

And then do

FILE *fp = fopen(filename, "wb");
BadIdea* foobar = new BadIdea(fp);

bool ok = TooJpeg::writeJpeg(foobar->writeByte, image, width, height);
delete foobar;
fflush(fp);
fclose(fp);

However, contrary to expectations, this does not work.

The reason is, C++ member functions are kinda implemented like C# extension functions.

So you have

class/struct BadIdea
{
    FILE* m_stream;
}

and

static class BadIdeaExtensions
{
    public static writeByte(this BadIdea instance, unsigned char oneByte)
    {
         fputc(oneByte, instance->m_stream);
    }

}

So when you want to call writeByte, you need pass not only the address of writeByte, but also the address of the BadIdea-instance.

So when you have a typedef for the writeByte procedure, and it looks like this

typedef void (*WRITE_ONE_BYTE)(unsigned char);

And you have a writeJpeg signature that looks like this

bool writeJpeg(WRITE_ONE_BYTE output, uint8_t* pixels, uint32_t 
 width, uint32_t height))
    { ... }

it's fundamentally impossible to pass a two-address member function to a one-address function pointer (without modifying writeJpeg), and there's no way around it.

The next best thing that you can do in C++, is using a lambda-function:

FILE *fp = fopen(filename, "wb");
auto lambda = [fp](unsigned char oneByte) { fputc(oneByte, fp);  };
bool ok = TooJpeg::writeJpeg(lambda, image, width, height);

However, because lambda is doing nothing different, than passing an instance to a hidden class (such as the "BadIdea"-class), you need to modify the signature of writeJpeg.

The advantage of lambda over a manual class, is that you just need to change one typedef

typedef void (*WRITE_ONE_BYTE)(unsigned char);

to

using WRITE_ONE_BYTE = std::function<void(unsigned char)>; 

And then you can leave everything else untouched.

You could also use std::bind

auto f = std::bind(&BadIdea::writeByte, &foobar);

But this, behind the scene, just creates a lambda function, which then also needs the change in typedef.

So no, there is no way to pass a member function to a method that requires a static function-pointer.

But lambdas are the easy way around, provided that you have control over the source.
Otherwise, you're out of luck.
There's nothing you can do with C++.

Note:
std::function requires #include <functional>

However, since C++ allows you to use C as well, you can do this with libffcall in plain C, if you don't mind linking a dependency.

Download libffcall from GNU (at least on ubuntu, don't use the distro-provided package - it is broken), unzip.

./configure
make
make install

gcc main.c -l:libffcall.a -o ma

main.c:

#include <callback.h>

// this is the closure function to be allocated 
void function (void* data, va_alist alist)
{
     int abc = va_arg_int(alist);

     printf("data: %08p\n", data); // hex 0x14 = 20
     printf("abc: %d\n", abc);

     // va_start_type(alist[, return_type]);
     // arg = va_arg_type(alist[, arg_type]);
     // va_return_type(alist[[, return_type], return_value]);

    // va_start_int(alist);
    // int r = 666;
    // va_return_int(alist, r);
}



int main(int argc, char* argv[])
{
    int in1 = 10;

    void * data = (void*) 20;
    void(*incrementer1)(int abc) = (void(*)()) alloc_callback(&function, data);
    // void(*incrementer1)() can have unlimited arguments, e.g. incrementer1(123,456);
    // void(*incrementer1)(int abc) starts to throw errors...
    incrementer1(123);
    // free_callback(callback);
    return EXIT_SUCCESS;
}

And if you use CMake, add the linker library after add_executable

add_library(libffcall STATIC IMPORTED)
set_target_properties(libffcall PROPERTIES
        IMPORTED_LOCATION /usr/local/lib/libffcall.a)
target_link_libraries(BitmapLion libffcall)

or you could just dynamically link libffcall

target_link_libraries(BitmapLion ffcall)

Note:
You might want to include the libffcall headers and libraries, or create a cmake project with the contents of libffcall.

梨涡少年 2024-07-18 03:54:30

一个简单的解决方案“解决方法”仍然是创建一个虚拟函数“接口”类并在调用者类中继承它。 然后将其作为参数“可以在您想要回调调用者类的另一个类的构造函数中”传递。

DEFINE Interface:

class CallBack 
{
   virtual callMeBack () {};
};

这是你要回调的类:

class AnotherClass ()
{
     public void RegisterMe(CallBack *callback)
     {
         m_callback = callback;
     }

     public void DoSomething ()
     {
        // DO STUFF
        // .....
        // then call
        if (m_callback) m_callback->callMeBack();
     }
     private CallBack *m_callback = NULL;
};

这是将被回调的类。

class Caller : public CallBack
{
    void DoSomthing ()
    {
    }

    void callMeBack()
    {
       std::cout << "I got your message" << std::endl;
    }
};

A simple solution "workaround" still is to create a class of virtual functions "interface" and inherit it in the caller class. Then pass it as a parameter "could be in the constructor" of the other class that you want to call your caller class back.

DEFINE Interface:

class CallBack 
{
   virtual callMeBack () {};
};

This is the class that you want to call you back:

class AnotherClass ()
{
     public void RegisterMe(CallBack *callback)
     {
         m_callback = callback;
     }

     public void DoSomething ()
     {
        // DO STUFF
        // .....
        // then call
        if (m_callback) m_callback->callMeBack();
     }
     private CallBack *m_callback = NULL;
};

And this is the class that will be called back.

class Caller : public CallBack
{
    void DoSomthing ()
    {
    }

    void callMeBack()
    {
       std::cout << "I got your message" << std::endl;
    }
};
冷默言语 2024-07-18 03:54:30

我可以看到 init 具有以下覆盖:

Init(CALLBACK_FUNC_EX callback_func, void * callback_parm)

其中 CALLBACK_FUNC_EX

typedef void (*CALLBACK_FUNC_EX)(int, void *);

I can see that the init has the following override:

Init(CALLBACK_FUNC_EX callback_func, void * callback_parm)

where CALLBACK_FUNC_EX is

typedef void (*CALLBACK_FUNC_EX)(int, void *);
七婞 2024-07-18 03:54:30

指向非静态成员函数的指针的类型与指向普通函数的指针不同。
如果它是普通静态成员函数,则类型为void(*)(int)
如果它是一个非静态成员函数,则类型为void(CLoggersInfra::*)(int)
因此,如果非静态成员函数需要普通函数指针,则不能将指针传递给该函数。

此外,非静态成员函数具有对象的隐式/隐藏参数。 this 指针作为参数隐式传递给成员函数调用。 因此,只能通过提供对象来调用成员函数。

如果 API Init 无法更改,则可以使用调用该成员的包装函数(普通函数或类静态成员函数)。 在最坏的情况下,该对象将是包装函数要访问的全局对象。

CLoggersInfra* pLoggerInfra;

RedundencyManagerCallBackWrapper(int val)
{
    pLoggerInfra->RedundencyManagerCallBack(val);
}
m_cRedundencyManager->Init(RedundencyManagerCallBackWrapper);

如果 API Init 可以更改,则有许多替代方案 - 对象非静态成员函数指针、函数对象、std::function 或接口功能。

请参阅关于 回调 的帖子,了解 C++ 工作示例

The type of pointer to non-static member function is different from pointer to ordinary function.
Type is void(*)(int) if it’s an ordinary or static member function.
Type is void(CLoggersInfra::*)(int) if it’s a non-static member function.
So you cannot pass a pointer to a non-static member function if it is expecting an ordinary function pointer.

Furthermore, a non-static member function has an implicit/hidden parameter to the object. The this pointer is implicitly passed as an argument to the member function call. So the member functions can be invoked only by providing an object.

If the API Init cannot be changed, a wrapper function (ordinary function or a class static member function) that invokes the member can be used. In the worst case, the object would be a global for the wrapper function to access.

CLoggersInfra* pLoggerInfra;

RedundencyManagerCallBackWrapper(int val)
{
    pLoggerInfra->RedundencyManagerCallBack(val);
}
m_cRedundencyManager->Init(RedundencyManagerCallBackWrapper);

If the API Init can be changed, there are many alternatives - Object non-static member function pointer, Function Object, std::function or Interface Function.

See the post on callbacks for the different variations with C++ working examples.

隱形的亼 2024-07-18 03:54:30

问题与解答来自C++ FAQ Lite 很好地涵盖了您的问题以及答案中涉及的注意事项,我认为。 我链接的网页的简短片段:

不要。

因为如果没有对象来调用,成员函数就没有意义
打开它,你不能直接执行此操作(如果 X Window 系统是
用 C++ 重写,它可能会传递对周围对象的引用,
不仅仅是指向函数的指针; 自然地,这些物体会体现
所需的功能,可能还有更多)。

This question and answer from the C++ FAQ Lite covers your question and the considerations involved in the answer quite nicely I think. Short snippet from the web page I linked:

Don’t.

Because a member function is meaningless without an object to invoke
it on, you can’t do this directly (if The X Window System was
rewritten in C++, it would probably pass references to objects around,
not just pointers to functions; naturally the objects would embody the
required function and probably a whole lot more).

囚我心虐我身 2024-07-18 03:54:30

看起来像 std::mem_fn ( C++11) 正是您所需要的:

函数模板 std::mem_fn 为指向成员的指针生成包装对象,它可以存储、复制和调用指向成员的指针。 调用 std::mem_fn 时可以使用对象的引用和指针(包括智能指针)。

Looks like std::mem_fn (C++11) does exactly what you need:

Function template std::mem_fn generates wrapper objects for pointers to members, which can store, copy, and invoke a pointer to member. Both references and pointers (including smart pointers) to an object can be used when invoking a std::mem_fn.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文