2012-01-03 11 views
15

All'interno di una gerarchia di classi C++, è possibile applicare un requisito che una determinata funzione virtuale chiama sempre anche l'implementazione della sua classe base? (Come la catena dei costruttori di modo?)Richiesta di funzioni virtuali sovrascritte per chiamare le implementazioni di base

Sto osservando un caso in cui una gerarchia di classi profonde ha alcune funzioni di interfaccia comuni che ogni bambino sovrascrive. Vorrei che l'override di ogni classe derivata passasse attraverso la classe base. È semplice farlo esplicitamente con ad esempio il codice qui sotto, ma c'è il rischio che qualcuno che implementa una nuova classe derivata possa dimenticare di collegarsi alla base.

C'è qualche schema per far rispettare questo, in modo che il compilatore genererà un errore se un override non riesce a concatenare la base?

Così, in

class CAA 
{ 
    virtual void OnEvent(CEvent *e) { 
    // do base implementation stuff; 
    } 
} 

class CBB : public CAA 
{ 
    typedef CAA BaseClass; 
    virtual void OnEvent(CEvent *e) { 
     DoCustomCBBStuff(); 
     BaseClass::OnEvent(e); // chain to base 
    } 
} 

class CCC : public CBB 
{ 
    typedef CBB BaseClass; 
    virtual void OnEvent(CEvent *e) { 
     Frobble(); 
     Glorp(); 
     BaseClass::OnEvent(e); // chain to CBB which chains to CAA, etc 
    } 
} 

class CDD : public CCC 
{ 
    typedef CCC BaseClass; 
    virtual void OnEvent(CEvent *e) { 
     Meep(); 
     // oops! forgot to chain to base! 
    } 
} 

c'è un modo, un po 'di trucco o modello espediente sintattico, per fare CDD gettare un errore più evidente?

+1

Non che io sappia, ma si può sempre dare le baseclass una funzione non virtuale e memorizzare un elenco di puntatori a funzione, al quale ogni classe derivata aggiunge la sua parte. –

+0

Effettivamente un duplicato di [Come forzare la stessa funzione virtuale del figlio chiama prima la sua funzione virtuale padre] (http://stackoverflow.com/questions/5644338/how-to-force-child-same-virtual-function-call-its -parent-virtual-function-first) –

+0

possibile duplicato di [C++: chiamare automaticamente un metodo di classe base?] (http://stackoverflow.com/questions/3107974/c-call-a-base-class-method-automatically) – outis

risposta

9

Il modo in cui viene eseguito il metodo della classe base non è virtuale e chiama un metodo virtuale protetto.

Ovviamente, gestisce solo un livello.

Nella tua situazione particolare, una grande quantità di infrastrutture può farlo funzionare, ma non ne vale la pena.

La risposta tipica è quella di aggiungere un commento

// Always call base class method 
+1

Quello era un intervallo di tempo tra la risposta e l'accettazione. – Joshua

0

Non c'è niente di far rispettare direttamente una funzione preminente di fare qualsiasi cosa in particolare, tranne la restituzione di un certo tipo. Tuttavia, se si crea la funzione virtuale della classe base private, nessuna funzione può chiamarla sulla classe base, ma le classi derivate possono sovrascriverla. Fornisci anche una funzione public che chiama la funzione virtuale e una funzione che esegue la logica della classe base. La logica della classe base probabilmente dovrebbe andare in una funzione separata (probabilmente la funzione di inoltro non virtuale direttamente) per evitare di eseguirla due volte se l'oggetto in realtà è un oggetto di base.

+0

Il problema è che copre solo un livello, ovvero che tutte le chiamate derivate chiameranno la funzione base definitiva, ma tutte le classi intermedie non partecipano a quella parte. – Xeo

+0

@Xeo: concordato. Non sono a conoscenza di alcun approccio che imponga il concatenamento di funzioni virtuali. Sebbene io che l'idea di concatenare la funzione di override sia carina (è usata ad esempio in Xt, un sistema GUI orientato agli oggetti per X11 implementato in C; tutte le cose orientate agli oggetti dovevano essere fatte manualmente), non avevo bisogno di altro che costruzione e distruzione che sono incatenate. –

1

Non v'è alcun supporto per questo nel linguaggio C++, ma in espansione sul commento di KerrekSB, si potrebbe fare qualcosa di simile:

class A { 
public: 
    void DoEvent(int i) { 
     for (auto event = events.begin(); event != events.end(); ++event) 
      (this->*(*event))(i); 
    } 

protected: 
    typedef void (A::*Event)(int); 

    A(Event e) { 
     events.push_back(&A::OnEvent); 
     events.push_back(e); 
    } 

    void OnEvent(int i) { 
     cout << "A::OnEvent " << i << endl; 
    } 

    vector<Event> events; 
}; 

class B : public A { 
public: 
    B() : A((Event)&B::OnEvent) { } 

protected: 
    B(Event e) : A((Event)&B::OnEvent) { 
     events.push_back(e); 
    } 

    void OnEvent(int i) { 
     cout << "B::OnEvent " << i << endl; 
    } 
}; 

class C : public B { 
public: 
    C() : B((Event)&C::OnEvent) { } 

protected: 
    C(Event e) : B((Event)&C::OnEvent) { 
     events.push_back(e); 
    } 

    void OnEvent(int i) { 
     cout << "C::OnEvent " << i << endl; 
    } 
}; 

Quindi utilizzare in questo modo

int main() { 
    A* ba = new B; 
    ba->DoEvent(32); 

    B* bb = new B; 
    bb->DoEvent(212); 

    A* ca = new C; 
    ca->DoEvent(44212); 

    B* cb = new C; 
    cb->DoEvent(2); 

    C* cc = new C; 
    cc->DoEvent(9); 
} 

che emette

A::OnEvent 32 
B::OnEvent 32 

A::OnEvent 212 
B::OnEvent 212 

A::OnEvent 44212 
B::OnEvent 44212 
C::OnEvent 44212 

A::OnEvent 2 
B::OnEvent 2 
C::OnEvent 2 

A::OnEvent 9 
B::OnEvent 9 
C::OnEvent 9 

Devi fare un po 'di lavoro, ma non devi chiamare manualmente il membro di base funzione alla fine di ogni chiamata. Here is the live demo.

+0

Questo è ciò a cui alludevo come troppo lavoro, ma funziona. – Joshua

+0

@Joshua sfortunatamente è il meglio che puoi fare senza ricorrere alla convenzione. –

3

Inserire un tipo speciale "nascosto" nella classe Base con un costruttore privato, utilizzando friend per garantire che solo la Base possa crearlo. This code on ideone.

Se ci sono più livelli, questo purtroppo non garantisce che venga chiamata la classe base immediata. Quindi struct E : public D; potrebbe implementare E::foo() con una chiamata a B:: foo quando si preferisce una chiamata a D::foo().

struct Base { 
     struct Hidden { 
       friend class Base; 
       private: 
         Hidden() {} 
     }; 
     virtual Hidden foo() { 
       cout << "Base" << endl; 
       return Hidden(); // this can create a Hidden 
     } 
}; 

struct D : public B { 
     virtual Hidden foo() { 
       cout << "D" << endl; 
       // return Hidden(); // error as the constructor is private from here 
       return B :: foo(); 
     } 
}; 

Se si è tentato di implementare D :: foo() senza un ritorno o con return Hidden(), si otterrà un messaggio di errore. L'unico modo per compilarlo è usare return B :: foo().

+0

Cuty ... ma leggermente imperfetto. 'pippo()' dovrebbe restituire un riferimento const e impedire l'esecuzione di copie di 'Nascosto'. Naturalmente, la soluzione è già buona per prevenire omissioni accidentali, in particolare rinominando 'Nascosto' in' DoNotForgetToChain' o qualcosa del genere. –

3

Seguire una semplice regola per ottenere una classe template è possibile.

rendimenti
#include <iostream> 

struct TEvent 
{ 
}; 

struct Base { 
    virtual void CallOnEvent(TEvent * e) 
    { 
     OnEvent(e); 
    } 
    virtual void OnEvent(TEvent * e) 
    { 
     std::cout << "Base::Event" << std::endl; 
    } 
    void CallUp(TEvent * e) 
    { 
    } 

}; 

template <typename B> 
struct TDerived : public B 
{ 
    void CallUp(TEvent * e) 
    { 
     B::CallUp(e); 
     B::OnEvent(e); 
    } 
    virtual void CallOnEvent(TEvent * e) 
    { 
     CallUp(e); 
     this->OnEvent(e); 
    } 
}; 

struct Derived01 : public TDerived<Base> 
{ 
    void OnEvent(TEvent * e) 
    { 
     std::cout << "Derived01::Event" << std::endl; 
    } 
}; 

struct Derived02 : public TDerived<Derived01> 
{ 
    void OnEvent(TEvent * e) 
    { 
     std::cout << "Derived02::Event" << std::endl; 
    } 
}; 

struct Derived03 : public TDerived<Derived02> 
{ 
    void OnEvent(TEvent * e) 
    { 
     std::cout << "Derived03::Event" << std::endl; 
    } 
}; 

struct Derived04 : public TDerived<Derived03> 
{ 
    void OnEvent(TEvent * e) 
    { 
     std::cout << "Derived04::Event" << std::endl; 
    } 
}; 


int main(void) 
{ 
Derived04 lD4; 
lD4.CallOnEvent(0); 
return 0; 
} 

questo codice (codepad):

Base::Event 
Derived01::Event 
Derived02::Event 
Derived03::Event 
Derived04::Event 

Per quanto riguarda alcune risposte utilizzando typeid. Non prenderei mai in considerazione l'utilizzo di typeid per qualcosa di diverso dal debug. Ciò è dovuto a due cose:

  • controllo di tipo dinamico può essere fatto in modo molto più efficienti (senza creare type_info oggetto utilizzando cioè dynamic_cast, alcuni metodi
  • C++ norma garantisce sostanzialmente soltanto l'esistenza di typeid, ma non proprio nulla per quanto riguarda come funziona (la maggior parte delle cose sono "compilatore specifico")

edit:

Un po 'più esempio complesso con ereditarietà multipla. Questo purtroppo non è risolvibile senza chiamate esplicite in classi che ereditano da più basi (principalmente perché non è chiaro cosa dovrebbe accadere in questi casi, quindi dobbiamo definire esplicitamente il comportamento).

#include <iostream> 

struct TEvent 
{ 
}; 

struct Base { 
    virtual void CallOnEvent(TEvent * e) 
    { 
     OnEvent(e); 
    } 
    virtual void OnEvent(TEvent * e) 
    { 
     std::cout << "Base::Event" << std::endl; 
    } 

    void CallUp(TEvent * e) 
    { 
    } 
}; 

template <typename B > 
struct TDerived : public B 
{ 
    void CallUp(TEvent * e) 
    { 
     B::CallUp(e); 
     B::OnEvent(e); 
    } 
    virtual void CallOnEvent(TEvent * e) 
    { 
     CallUp(e); 
     this->OnEvent(e); 
    } 
}; 

struct Derived01 : virtual public TDerived<Base> 
{ 
    void OnEvent(TEvent * e) 
    { 
     std::cout << "Derived01::Event" << std::endl; 
    } 
}; 

struct Derived02 : virtual public TDerived<Derived01> 
{ 
    void OnEvent(TEvent * e) 
    { 
     std::cout << "Derived02::Event" << std::endl; 
    } 
}; 

typedef TDerived<Derived02> TDerived02; 
typedef TDerived<Derived01> TDerived01; 
struct Derived03 : virtual public TDerived02, virtual public TDerived01 
{ 
    void OnEvent(TEvent * e) 
    { 
     std::cout << "Derived03::Event" << std::endl; 
    } 

    virtual void CallOnEvent(TEvent * e) 
    { 
     CallUp(e); 
     Derived03::OnEvent(e); 
    } 
    void CallUp(TEvent * e) 
    { 
     TDerived02::CallUp(e); 
     TDerived01::CallUp(e); 
    } 
}; 

struct Derived04 : public TDerived<Derived03> 
{ 
    void OnEvent(TEvent * e) 
    { 
     std::cout << "Derived04::Event" << std::endl; 
    } 
}; 


int main(void) 
{ 
Derived04 lD4; 
Derived03 lD3; 

lD3.CallOnEvent(0); 
std::cout << std::endl; 
lD4.CallOnEvent(0); 

return (0); 
} 

risultato è (ideone):

Base::Event  \     \ 
Derived01::Event | - from Derived02 | 
Derived02::Event/    |-- from Derived03 
Base::Event  \__ from Derived01 | 
Derived01::Event/    | 
Derived03::Event     /

Base::Event  \     \     \ 
Derived01::Event | - from Derived02 |     | 
Derived02::Event/    |-- from Derived03 |-- from Derived04 
Base::Event  \__ from Derived01 |     | 
Derived01::Event/    |     | 
Derived03::Event     /    | 
Derived04::Event          /
Problemi correlati