2012-08-08 73 views
5

想象一下,你有一個類層次結構如下所示:建議如何從包含的類中調用在容器類中定義的回調函數?

class Robot 
{ 
public: 
    void OnTaskCompleted() {} 

private: 
    Task *m_pTask; 
}; 

class Task 
{ 
public: 
    virtual void DoTask() = 0; 
}; 

class TidyUp : public Task 
{ 
public: 
    void DoTask() 
    { 
     // When TidyUp task is compeleted invoke OnTaskCompleted() from here. 
    } 
}; 

我需要調用OnTaskCompleted()TidyUp::DoTask()。推薦的方法是什麼?

我想避免:

  • 使OnTaskCompleted()靜態
  • 傳遞機器人指針任務
+0

誰打電話'DoTask'? – 2012-08-08 10:52:02

+1

你可以在'std :: function '中傳遞'std :: bind(&Robot :: OnTaskCompleted,this)'。 – Xeo 2012-08-08 10:52:32

+0

@ AndreasBrinck-我現在編輯了我的代碼。 – jpen 2012-08-08 10:55:37

回答

3

static路由不可行,除非程序中只有一個Robot,並且該機器人的一個實例可以靜態使用。

Robot傳遞給任務可能沒問題,但它可能會泄露太多信息並禁止使用機器人以外的對象的任務用法。

第三種方法是爲完成通知製作一個類似界面的類,將其擴展到Robot,並從任務中調用它。不幸的是,通過推動你進入虛擬繼承領域,C++並不是特別容易。

你可以採用POSIX線程庫中常見的回調方法(傳遞一個void指針和一個接受void指針的函數指針),但這不是太C++ - ish。最後,如果您使用的是C++ 11,那麼您可以使用anonymous functions,它可以非常優雅地解決該問題,方法是在不使用外部庫的情況下封裝一個函數和它在其中運行的對象,例如促進。

這裏是第三種方法(link to ideone)的一個簡單的例子:

#include <iostream> 
#include <string> 
using namespace std; 

class WithNotification { 
public: 
    virtual void notify()=0; 
}; 

class Robot : public virtual WithNotification { 
private: 
    string name; 
public: 
    Robot(const string& n) : name(n) {} 
    virtual void notify() {cout << name << " has been notified" << endl; } 
}; 

class Task { 
private: 
    WithNotification& onFinished; 
public: 
    Task(WithNotification& f) : onFinished(f) {} 
    void run() { 
     cout << "The task is running" << endl; 
     onFinished.notify(); 
    } 
}; 

int main() { 
    Robot r1("Quick"); 
    Robot r2("Brown"); 
    Task t1(r1); 
    Task t2(r2); 
    t1.run(); 
    t2.run(); 
} 
+0

@ dasblinkenlight - 我喜歡你的第三個選項的聲音。你能給我一個例子嗎? – jpen 2012-08-08 11:08:20

+0

@jpen當然,看看。 – dasblinkenlight 2012-08-08 11:19:52

+0

@ dasblinkenlight - 謝謝你! +1 – jpen 2012-08-08 11:27:09

0

我會去傳遞一個指針。但是,如果你打算使用Task不僅Robot考慮制定TaskDelegate接口用於發送通知:

class TaskDelegate 
{ 
public: 
    virtual void onTaskFinished(Task *sender) = 0; //implement in successors 

protected: 
    ~TaskDelegate() {} //don't allow deletion via interface pointer 
}; 

現在你Task類看起來像:

class Task 
{ 
    Task(TaskDelegate *delegate) {...} 
    ... 
}; 

繼承Robot是一個delegate

class Robot : public TaskDelegate {...} 

因此Task能夠通知實現的所有對象TaskDelegate接口

0

沒有任何方法可以解決這個問題,而不需要傳遞任何東西給包含的類。最簡單的方法是讓Robot從包含OnTaskCompleted方法的純虛擬基類繼承,並且讓Task類具有指向該虛擬基類的指針或引用,並將Robot實例傳遞給包含的Task類。 Robot類不是從基類繼承的,而是可以將其作爲模板參數傳遞給模板化的Task類。

另一種解決方案是將Task作爲模板類,並通過this->OnTaskCompleted函數作爲模板參數。就像:

template<class Callback> 
class Task 
{ 
public: 
    void DoTask() 
    { 
     // ... 

     Callback(); 
    } 
}; 

class Robot 
{ 
public: 
    Robot() 
    { 
     m_pTask = new Task<this->OnTaskCompleted>; 
    } 

    // ... 
}; 

如果這最後一種方法的作品,我其實並不知道,因爲我還沒有嘗試過。

正如你所看到的,無論從我的和其他的答案,有很多方法來做到這一點。他們中沒有一個實際上是一個「官方」或「推薦」的方式來做到這一點。但幾乎所有這些都需要你通過一個類或一個對象到類Task類。

0

根據XEO的建議,我已經做了這樣的事情:

#include <iostream> 
#include <functional> 

typedef std::tr1::function<void (int)> CallbackFunc; 

class Task 
{ 
public: 
    Task(CallbackFunc callbackFunc) : m_callbackFunc(callbackFunc) {} 
    virtual ~Task() {} 
    virtual void DoTask() = 0; 
protected: 
    CallbackFunc m_callbackFunc; 
}; 

class TidyUp : public Task 
{ 
public: 
    TidyUp(CallbackFunc callbackFunc) : Task(callbackFunc) {} 
    void DoTask() { 
     std::cout << "I love tidying up!" << std::endl; 
     m_callbackFunc(6); // When TidyUp task is compeleted invoke OnTaskCompleted() from here. 
    } 
}; 

class Robot : private Uncopyable 
{ 
public: 
    Robot() : m_pTask(new TidyUp(std::bind(&Robot::OnTaskCompleted, this, std::placeholders::_1))) {} 
    ~Robot() { delete m_pTask; } 
    void DoTask() { m_pTask->DoTask(); } 
    void OnTaskCompleted(int nCleannessLevel) { std::cout << "Done! Cleanness Level: " << nCleannessLevel << std::endl; } 
private: 
    Task *m_pTask; 
}; 

int main() 
{ 
    Robot robot; 
    robot.DoTask(); 
    return 0; 
} 
+0

你在'Robot'類中忽略了[三條規則](http://stackoverflow.com/q/4172722/500104)。如果您製作副本,則需要進行雙重刪除。另外,應用[Non-Virtual Interface idiom](http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Non-Virtual_Interface)將有助於確保始終調用回調。 – Xeo 2012-08-09 08:18:38

+0

感謝您指出。 – jpen 2012-08-09 09:26:12

+0

@ Xeo - 我希望我的OnTaskCompleted()等價函數是私有的。我已經把它變成了私有的(它編譯得很好併產生相同的輸出結果),但我現在想知道這是否是一個有效的設計。我的意思是m_callbackFunc()可以調用這個私人回調函數。這是一個糟糕的設計? – jpen 2012-08-09 15:47:32

相關問題