使用类成员的C++回调函数

107

我知道这个问题已经被问了很多次,因此很难深入挖掘并找到一个简单的示例来解决问题。

我有一个简单的解决方案,可用于 MyClass ...

#include <iostream>
using std::cout;
using std::endl;

class MyClass
{
    public:
        MyClass();
        static void Callback(MyClass* instance, int x);
    private:
        int private_x;
};

class EventHandler
{
    public:
        void addHandler(MyClass* owner)
        {
            cout << "Handler added..." << endl;
            //Let's pretend an event just occured
            owner->Callback(owner,1);
        }
};

EventHandler* handler;

MyClass::MyClass()
{
    private_x = 5;
    handler->addHandler(this);
}

void MyClass::Callback(MyClass* instance, int x)
{
    cout << x + instance->private_x << endl;
}

int main(int argc, char** argv)
{
    handler = new EventHandler();
    MyClass* myClass = new MyClass();
}

class YourClass
{
    public:
        YourClass();
        static void Callback(YourClass* instance, int x);
};

如何重写EventHandler::addHandler()函数,使其适用于MyClassYourClass。抱歉,这是我的大脑工作方式,我需要看到一个简单的可行示例,然后才能理解为什么/如何起作用。如果你有一种喜欢的方法可以让这个函数工作,请标记代码并将其回复。 [编辑] 问题已经得到解答,但在我确认选项之前,该答案已被删除。 对我来说答案是使用模板化函数。将addHandler更改为以下内容...
class EventHandler
{
    public:
        template<typename T>
        void addHandler(T* owner)
        {
            cout << "Handler added..." << endl;
            //Let's pretend an event just occured
            owner->Callback(owner,1);
        }
};

4
谁发布了函数模板示例?你得到了勾号,但是在我测试时,你删除了你的答案。它恰好符合我的需要。一个简单的函数模板在我阅读的所有其他信息中丢失了。您的答案作为编辑添加到问题中。 - BentFX
我想是JaredC干的。你也许需要追查一下他 =P - WhozCraig
6个回答

200

可以使用新的C++11标准中的功能:std::functionstd::bind,而不是具有静态方法并传递类实例指针的方式:

#include <functional>
class EventHandler
{
    public:
        void addHandler(std::function<void(int)> callback)
        {
            cout << "Handler added..." << endl;
            // Let's pretend an event just occured
            callback(1);
        }
};

addHandler方法现在接受一个std::function类型的参数,这个“函数对象”没有返回值且接受一个整数作为参数。

要绑定到特定的函数,可以使用std::bind

class MyClass
{
    public:
        MyClass();

        // Note: No longer marked `static`, and only takes the actual argument
        void Callback(int x);
    private:
        int private_x;
};

MyClass::MyClass()
{
    using namespace std::placeholders; // for `_1`

    private_x = 5;
    handler->addHandler(std::bind(&MyClass::Callback, this, _1));
}

void MyClass::Callback(int x)
{
    // No longer needs an explicit `instance` argument,
    // as `this` is set up properly
    cout << x + private_x << endl;
}

在添加处理程序时,您需要使用std::bind,因为您需要明确指定隐式的 this 指针作为参数。如果有一个独立的自由函数,您不必使用 std::bind

void freeStandingCallback(int x)
{
    // ...
}

int main()
{
    // ...
    handler->addHandler(freeStandingCallback);
}

使用std :: function对象作为事件处理程序,也可以使用新的C ++11lambda函数

handler->addHandler([](int x) { std::cout << "x is " << x << '\n'; });

4
谢谢Joachim!这个例子大大地解开了std::function和std::bind的神秘面纱。我以后一定会用到它![编辑]我仍然完全不理解lambda :) - BentFX
3
我把这个东西加入了我更大的项目中(大约有6,000行代码,对于我来说是很大的)。它使用不同回调函数和参数的按钮定义向量,并将其提供给wxWidgets,以便对象可以在wxFrame中管理自己的按钮。这使事情变得简单得多!我无法再强调一遍,互联网上包含太多技术性和观点,而缺乏足够简单的示例。 - BentFX
1
@user819640,没有“unbind”,而是std::bind只返回一个(未指定的)对象,当你完成它时,可以让它超出范围。如果绑定的对象被销毁并且您尝试调用该函数,则会得到未定义行为 - Some programmer dude
2
handler->addHandler() 的意思是你在某处创建了一个 EventHandler 对象?好答案,加一分。 - gsamaras
2
请注意,占位符的数量必须与参数的数量匹配,因此如果回调函数中有两个参数,则需要使用...., _1, _2)等。 - Den-Jason
显示剩余10条评论

8
这是一个简洁的版本,可用于类方法回调和常规函数回调。在这个例子中,为了展示如何处理参数,回调函数接受两个参数:boolint。请保留HTML标记。
class Caller {
  template<class T> void addCallback(T* const object, void(T::* const mf)(bool,int))
  {
    using namespace std::placeholders; 
    callbacks_.emplace_back(std::bind(mf, object, _1, _2));
  }
  void addCallback(void(* const fun)(bool,int)) 
  {
    callbacks_.emplace_back(fun);
  }
  void callCallbacks(bool firstval, int secondval) 
  {
    for (const auto& cb : callbacks_)
      cb(firstval, secondval);
  }
private:
  std::vector<std::function<void(bool,int)>> callbacks_;
}

class Callee {
  void MyFunction(bool,int);
}

//then, somewhere in Callee, to add the callback, given a pointer to Caller `ptr`

ptr->addCallback(this, &Callee::MyFunction);

//or to add a call back to a regular function
ptr->addCallback(&MyRegularFunction);

这将C++11特定的代码限制在addCallback方法和Caller类中的私有数据中。至少对我来说,这最大程度地减少了实现时出错的机会。

请注意,使用C++20的bind_front,您可以简化类成员函数的add_callback为:

      template<class T> void addCallback(T* const object, void(T::* const mf)(bool,int))
      {
        callbacks_.emplace_back(std::bind_front(mf, object));
      }

4

以下是针对C++11的完整工作示例,基于上述代码:

#include <stdlib.h>
#include <stdio.h>
#include <functional>

#if __cplusplus <= 199711L
  #error This file needs at least a C++11 compliant compiler, try using:
  #error    $ g++ -std=c++11 ..
#endif

using namespace std;

class EventHandler {
    public:
        void addHandler(std::function<void(int)> callback) {
            printf("\nHandler added...");
            // Let's pretend an event just occured
            callback(1);
        }
};


class MyClass
{
    public:
        MyClass(int);
        // Note: No longer marked `static`, and only takes the actual argument
        void Callback(int x);

    private:
        EventHandler *pHandler;
        int private_x;
};

MyClass::MyClass(int value) {
    using namespace std::placeholders; // for `_1`

    pHandler = new EventHandler();
    private_x = value;
    pHandler->addHandler(std::bind(&MyClass::Callback, this, _1));
}

void MyClass::Callback(int x) {
    // No longer needs an explicit `instance` argument,
    // as `this` is set up properly
    printf("\nResult:%d\n\n", (x+private_x));
}

// Main method
int main(int argc, char const *argv[]) {

    printf("\nCompiler:%ld\n", __cplusplus);
    new MyClass(5);
    return 0;
}


// where $1 is your .cpp file name... this is the command used:
// g++ -std=c++11 -Wall -o $1 $1.cpp
// chmod 700 $1
// ./$1

输出应该是:
Compiler:201103

Handler added...
Result:6

4
你需要做的是创建一个处理这段代码的接口,并让所有类都实现该接口。
class IEventListener{
public:
   void OnEvent(int x) = 0;  // renamed Callback to OnEvent removed the instance, you can add it back if you want.
};


class MyClass :public IEventListener
{
    ...
    void OnEvent(int x); //typically such a function is NOT static. This wont work if it is static.
};

class YourClass :public IEventListener
{

请注意,为了使这个方法工作,"回调"函数必须是非静态的,我认为这是一种改进。如果你想让它是静态的,你需要像JaredC建议的那样使用模板。

你只展示了其中一面。请展示如何触发事件。 - Christopher Pisz

1

MyClassYourClass都可以派生自SomeonesClass,它具有一个抽象(虚拟)的Callback方法。您的addHandler将接受类型为SomeonesClassMyClassYourClass的对象,而MyClassYourClass可以重写Callback以提供它们特定的回调行为实现。


针对我的工作,我曾考虑过这个想法。但因为使用我的处理程序的类别非常不同,所以我并没有将其视为一个选项。 - BentFX

0

如果您有带有不同参数的回调函数,可以使用以下模板:
// 使用以下命令编译:g++ -std=c++11 myTemplatedCPPcallbacks.cpp -o myTemplatedCPPcallbacksApp

#include <functional>     // c++11

#include <iostream>        // due to: cout


using std::cout;
using std::endl;

class MyClass
{
    public:
        MyClass();
        static void Callback(MyClass* instance, int x);
    private:
        int private_x;
};

class OtherClass
{
    public:
        OtherClass();
        static void Callback(OtherClass* instance, std::string str);
    private:
        std::string private_str;
};

class EventHandler
{

    public:
        template<typename T, class T2>
        void addHandler(T* owner, T2 arg2)
        {
            cout << "\nHandler added..." << endl;
            //Let's pretend an event just occured
            owner->Callback(owner, arg2);
         }   

};

MyClass::MyClass()
{
    EventHandler* handler;
    private_x = 4;
    handler->addHandler(this, private_x);
}

OtherClass::OtherClass()
{
    EventHandler* handler;
    private_str = "moh ";
    handler->addHandler(this, private_str );
}

void MyClass::Callback(MyClass* instance, int x)
{
    cout << " MyClass::Callback(MyClass* instance, int x) ==> " 
         << 6 + x + instance->private_x << endl;
}

void OtherClass::Callback(OtherClass* instance, std::string private_str)
{
    cout << " OtherClass::Callback(OtherClass* instance, std::string private_str) ==> " 
         << " Hello " << instance->private_str << endl;
}

int main(int argc, char** argv)
{
    EventHandler* handler;
    handler = new EventHandler();
    MyClass* myClass = new MyClass();
    OtherClass* myOtherClass = new OtherClass();
}

1
请问您能否解释一下您是如何解决 OP 的问题的?完整的代码是否真的有必要包含在 OP 中?OP 希望他的代码能够与他的 YourClass 一起工作。您似乎已经删除了该类并添加了另一个 OtherClass。此外,该问题已经有了一个很好的答案。您的解决方案有何优势,值得发布吗? - honk
我并没有说我的帖子是更好的解决方案。我展示了如何以模板方式使用“OtherClass”。 - mohDady

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接