2012-08-08 9 views
5

Immagina di avere una gerarchia di classi come la seguente:Qual è il modo consigliato per richiamare la funzione di callback definito nella classe contenitore dalla classe contenuta?

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

ho bisogno di chiamare OnTaskCompleted() da TidyUp::DoTask(). Quale sarebbe il modo consigliato per farlo?

vorrei evitare:

  • rendendo OnTaskCompleted() statico
  • passando il puntatore del robot per Task
+0

Chi sta chiamando 'DoTask'? –

+1

È possibile comprimerlo in una funzione 'std :: ' passando 'std :: bind (& Robot :: OnTaskCompleted, this)'. – Xeo

+0

@ AndreasBrinck-Ho ora modificato il mio codice. – jpen

risposta

3

La route static non è fattibile a meno che non ci sia un solo Robot nel programma e un'istanza di quel robot sia disponibile staticamente.

Il passaggio a Robot dell'attività può essere corretto, ma potrebbe rivelare troppe informazioni e vietare l'utilizzo di attività con oggetti diversi dai robot.

Una terza alternativa sarebbe quella di creare una classe simile all'interfaccia per le notifiche di completamento, estendendola nello Robot e richiamandola dall'attività.Sfortunatamente, il C++ non lo rende particolarmente facile spingendoti nel territorio dell'ereditarietà virtuale.

È possibile adottare un approccio di richiamo che è comune nelle librerie di thread POSIX (passando un puntatore del vuoto e un puntatore di funzione che accetta un puntatore del vuoto), ma che non è troppo C++ - ish.

Infine, se si utilizza C++ 11, si ha anonymous functions che consente di risolvere il problema in modo molto elegante avvolgendo sia una funzione che un oggetto su cui opera in una singola chiusura senza utilizzare una libreria esterna, ad esempio Incremento.

Ecco un rapido esempio del terzo approccio (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-Mi piace il suono della terza opzione. Saresti in grado di mostrarmi un esempio per questo, per favore? – jpen

+0

@jpen Certo, dai un'occhiata. – dasblinkenlight

+0

@ dasblinkenlight-Grazie per quello! +1 – jpen

0

vorrei andare con il superamento di un puntatore. Ma se si prevede di utilizzare Task non solo con Robot considerare la possibilità di un'interfaccia TaskDelegate per le notifiche di invio:

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

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

Ora la classe Task sarà simile:

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

Eredita Robot essere un delegate

class Robot : public TaskDelegate {...} 

quindi il Task è in grado di notificare qualsiasi oggetto che implementa l'interfaccia TaskDelegate

0

Non c'è modo di risolverlo senza passare nulla alla classe contenuta. Il più semplice è quello di avere Robot ereditato da una classe di base virtuale pura che contiene il metodo OnTaskCompleted e che la classe Task abbia un puntatore o un riferimento a questa classe di base virtuale e passi l'istanza Robot alla classe contenuta Task. Invece di avere l'eredita Robot classe da una classe base, si poteva passare come un parametro di template a una su modelli Task di classe.

Un'altra soluzione consiste nel rendere una classe modello Task e passare la funzione this->OnTaskCompleted come parametro modello. Qualcosa di simile:

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

     Callback(); 
    } 
}; 

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

    // ... 
}; 

Se quest'ultimo modo funziona, in realtà non ne ho idea, in quanto non l'ho provato.

Come si può vedere, sia dai miei e di altre risposte, ci sono molti modi per farlo. Nessuno di loro è in realtà un modo "ufficiale" o "raccomandati" per fare questo. Ma quasi tutti questi hanno bisogno di passare sia una classe o un oggetto attorno alla classe Task.

0

secondo il suggerimento di Xeo ho fatto qualcosa di simile:

#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

Stai trascurando la [regola dei tre] (http://stackoverflow.com/q/4172722/500104) nella tua classe 'Robot'. Se fai una copia, avrai delle doppie eliminazioni. Inoltre, l'applicazione dell'idioma [Interfaccia non virtuale] (http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Non-Virtual_Interface) aiuta a garantire che venga sempre richiamata la richiamata. – Xeo

+0

Grazie per averlo indicato. – jpen

+0

@ Xeo-Voglio che la mia funzione equivalente a OnTaskCompleted() sia privata. L'ho reso privato (compila bene e produce lo stesso risultato in uscita) ma ora mi chiedo se questo è un progetto valido. Voglio dire, m_callbackFunc() può chiamare in questa funzione di callback privata. È un cattivo design? – jpen

Problemi correlati