如何在C++中实现回调?

15
我想在C++中实现一个带回调的类。 我认为我需要一个具有两个参数的方法:
目标对象 (例如 myObj) 和指向目标对象成员函数的指针(这样我可以执行 *myObj->memberFunc(); )。
条件如下:
- myObj 可以是任何类。 - 将作为回调函数的成员函数是非静态的。
我一直在研究这个问题,但好像我需要事先知道 myObj 的类。 但我不确定该怎么做。 在C ++中是否可能实现这一点?
以下是我考虑过的,但肯定不正确的代码。
class MyClassWithCallback{
public
    void *targetObj;
    void (*callback)(int number);
    void setCallback(void *myObj, void(*callbackPtr)(int number)){
        targetObj = myObj;
        callback = callbackPtr;
    };
    void callCallback(int a){
        (myObj)->ptr(a);
    };
};
class Target{
public
    int res;
    void doSomething(int a){//so something here. This is gonna be the callback function};        
};

int main(){
    Target myTarget;
    MyClassWithCallback myCaller;
    myCaller.setCallback((void *)&myTarget, &doSomething);

}

感激任何帮助。

谢谢。

更新 大部分人都说是观察和委托,这正是我在寻找的,我有点儿Objective-C/Cocoa的思路。 我的当前实现是使用具有虚函数的接口。我想只传递对象和成员函数指针(像boost一样)可能更“聪明”,而不是定义一个接口。但似乎每个人都认为接口是最简单的方法?Boost似乎是个好主意(假设已经安装)。


1
观察者模式需要更改“被观察”类接口,有时使用functionbind而不是接口会更简单、更清晰。另外请注意,如果您使用的是最近版本的gcc,则已经具备了functionbind功能,因此无需使用boost库。使用function/bind是函数式编程模式,而使用接口则更多地使用“标准”设计模式,其含义不同。 - Artyom
6个回答

16

最佳方案,使用 boost::functionboost::bind,或者如果您的编译器支持 tr1/c++0x,则使用 std::tr1::functionstd::tr1::bind

这样就变得非常简单:

boost::function<void()> callback;
Target myTarget;
callback=boost::bind(&Target::doSomething,&myTarget);

callback(); // calls the function

而你的设置回调函数则变成:

class MyClassWithCallback{
public:
  void setCallback(boost::function<void()> const &cb)
  {
     callback_ = cb;
  }
  void call_it() { callback_(); }
private:
  boost::function<void()> callback_;
};
否则,您需要实现一些抽象类。
struct callback { 
 virtual void call() = 0;
 virtual ~callback() {}
};

struct TargetCallback {
 virtual void call() { ((*self).*member)()); }
 void (Target::*member)();
 Target *self;
 TargetCallback(void (Target::*m)(),Target *p) : 
       member(m),
       self(p)
 {}
};

然后使用:

myCaller.setCallback(new TargetCallback(&Target::doSomething,&myTarget));

当你的类被修改为:

class MyClassWithCallback{
public:
  void setCallback(callback *cb)
  {
     callback_.reset(cb);
  }
  void call_it() { callback_->call(); }
private:
  std::auto_ptr<callback> callback_;
};

当然,如果你想要调用的函数不改变,你可以实现一些接口,例如从某个抽象类中衍生出Target并进行此调用。


+1,boost::bind绝对是C++中回调的最佳选择。 - Calvin1602
目前我在我的iMac上使用gcc,但实际上我正在创建一个应该主要在Windows上编译的库。 - nacho4d
通常来说mingw中的gcc-4.x版本有这个功能,MSVC2010也应该有。 - Artyom
在g++ 4.5和msvc2010中,您可以使用lambda表达式代替bind。但是,您的代码将不兼容旧编译器。 - ejgottl
展示一种构建自己的回调函数的方法(不使用外部库),加1分。如果使用模板,会是什么样子? - Marchy
显示剩余2条评论

8

一个技巧是使用接口,这样你就不需要特别知道在'MyClassWithCallback'中的类,如果传入的对象实现了该接口。

例如(伪代码)

struct myinterface
{
  void doSomething()=0;
};

class Target : public myinterface { ..implement doSomething... };

并且

myinterface *targetObj; 
void setCallback(myinterface *myObj){
    targetObj = myObj;
};

执行回调函数

targetObj->doSomething();

设置它:

Target myTarget;
MyClassWithCallback myCaller;
myCaller.setCallback(myTarget);

5

看起来观察者设计模式是您正在寻找的。


4
您有几个基本选项:
1)指定回调将使用的类,以便在调用者中知道对象指针和成员函数指针类型,并可以使用。该类可能具有多个具有相同签名的成员函数,您可以在其中选择,但您的选择非常有限。
您在代码中做错的一件事是,在C ++中,成员函数指针和自由函数指针不是相同的,也不兼容。您的回调注册函数采用函数指针,但您试图传递成员函数指针。不允许。此外,“this”对象的类型是成员函数指针类型的一部分,因此在C ++中不存在“指向任何成员函数的指针,它接受一个整数并返回void”的概念。它必须是“指向Target的任何成员函数的指针,它接受一个整数并返回void”。因此,选项有限。
2)在接口类中定义一个纯虚拟函数。任何想要接收回调的类都可以继承该接口类。由于多重继承,这不会干扰您的类层次结构的其余部分。这几乎与Java中定义接口完全相同。
3)使用非成员函数进行回调。对于每个想要使用它的类,您编写一个小型存根自由函数,该函数需要对象指针并在其上调用正确的成员函数。因此,在您的情况下,您将拥有:
dosomething_stub(void *obj, int a) {
    ((Target *)obj)->doSomething(a);
}

4) Use templates:

template<typename CB> class MyClassWithCallback {
    CB *callback;
 public:
    void setCallback(CB &cb) { callback = &cb; }
    void callCallback(int a) {
        callback(a);
    }
};

class Target {
    void operator()(int a) { /* do something; */ }
};

int main() {
    Target t;
    MyClassWithCallback<T> caller;
    caller.setCallback(t);
}

您能否使用模板取决于您的ClassWithCallback是否是某个大型旧框架的一部分-如果是,则可能不可能(确切地说:可能需要一些更多的技巧,例如继承自非模板类具有虚拟成员函数的模板类),因为您不能保证为每个回调接收者实例化整个框架。


3

是的,我来自Objective-C的世界,这正是我想要实现的。 - nacho4d

2
在C++中,几乎不使用指向类方法的指针。你所说的委托及其使用是不建议的。相反,你必须使用虚函数和抽象类。 然而,如果C++不支持完全不同的编程概念,我可能不会那么喜欢它。如果你仍然想要委托,你应该看看“boost functional”(C++0x的一部分),它允许指向类方法的指针无论类名如何。此外,在C++ Builder中有类型__closure - 编译器级别的委托实现。 P.S.对于糟糕的英语表示歉意...

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