2010-08-01 98 views
11

我想在C++中實現一個有回調的類。如何在C++中實現回調?

所以我想我需要的是有2個參數的方法:

  • 目標對象。 (比方說 * 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); 

}

我感謝所有幫助。

謝謝。

UPDATE 你們大多數人都說觀察和授權,那麼我正是在尋找什麼,我是一個Objective-C/Cocoa頭腦的人。 我目前的實現是使用虛擬函數的接口。只是我認爲只是傳遞對象和成員函數指針(如boost!)而不是定義接口會更「聰明」。但似乎每個人都同意接口是最簡單的方法嗎? Boost似乎是一個好主意,(假設已安裝)

+0

觀察者模式需要在「觀察」類接口中進行更改,有時更簡單和更清晰地使用「函數」和「綁定」而不是接口。還要注意,如果你使用最近的gcc,你已經有'function'和'bind',所以不需要提升。使用函數/綁定是功能性編程模式,而使用接口更多的是使用具有不同含義的「標準」設計模式 – Artyom 2010-08-01 17:28:52

回答

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

當然如果你要調用不會改變,你可能只是實現一些接口的功能,即從該調用一些抽象類派生的目標。

+0

+1使用加速時尚漂亮 – 2010-08-01 12:25:49

+0

+1 ,boost :: bind絕對是C++的唯一途徑 - 只有回調 – Calvin1602 2010-08-01 12:39:56

+0

boost:bind肯定是找到的答案。我可能將不得不安裝boost。 – nacho4d 2010-08-01 12:46:06

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); 
+1

http://en.wikipedia.org/wiki/Observer_pattern – 2010-08-01 11:52:29

5

Observer設計模式似乎是你在找什麼。

2

在C++中,很難使用指向類方法的指針。你打來的這個事實 - 這是代表​​,他們的使用不推薦。而不是他們,你必須使用虛函數和抽象類。 但是,如果C++不支持完全不同的編程概念,C++就不會那麼喜歡我了。如果你仍然想要代表,你應該看看「boost功能」(C + +0 x的一部分),它允許指向類的方法,而不管類名是什麼。此外,在C++ Builder中有類型__closure - 在編譯器級別實現委託。

P.S.對不起,我英語不好...

4

你有幾個基本的選擇:

1)指定的回調要使用,從而使對象的指針和成員函數指針類型被稱爲什麼課,可用於呼叫者。該類可能有多個具有相同簽名的成員函數,您可以選擇它們,但是您的選項非常有限。

您在代碼中做了錯誤的一件事是C++中的成員函數指針和自由函數指針不相同,並且不是兼容類型。你的回調註冊函數接受一個函數指針,但你試圖傳遞一個成員函數指針。不允許。此外,「this」對象的類型是成員函數指針類型的一部分,因此C++中沒有這樣的「指向任何使用整數並返回void的成員函數的指針」。它必須是「一個指向目標的任何成員函數的指針,該函數接受一個整數並返回void。因此有限的選擇。

2)在接口類中定義一個純虛函數。因此任何想要接收回調的類都可以繼承接口類。感謝多重繼承,這不會影響到你的類層次結構的其餘部分。這與在Java中定義接口幾乎完全相同。

3)使用非成員函數進行回調。對於想要使用它的每個類,您可以編寫一個免費的小函數,它接受對象指針並在其上調用正確的成員函數。所以你的情況你必須:

dosomething_stub(void *obj, int a) { 
    ((Target *)obj)->doSomething(a); 
} 

4)使用模板:

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是一些大的舊框架的一部分 - 如果是的話,那麼它可能不可能(確切地說:可能需要一些更多的技巧,例如從具有虛擬成員函數的非模板類繼承的模板類),因爲您無需爲每個回調接收者實例化整個框架一次。