15

+ funzione virtuale Ho un diamante multipla scenario di eredità in questo modo:ereditarietà multipla pasticcio

A 
/ \ 
B  C 
    \ /
    D 

Il genitore comune, A, definisce una funzione fn virtuale().
E 'possibile che sia B che C definiscano fn()?
Se lo è, la domanda successiva è: D può accedere sia a B che a C fn() senza disambiguazione? Suppongo che ci sia qualche sintassi per questo ..
Ed è possibile che D lo faccia senza sapere specificamente chi sono B e C? B e C possono essere sostituiti da alcune altre classi e voglio che il codice in D sia generico.

Quello che sto cercando di fare è avere D in qualche modo enumerare tutte le istanze di fn() che ha nella sua discendenza. Questo è possibile in qualche altro modo che funzioni virtuali?

risposta

1

Ci sono già diverse domande che trattano questo. Sembra che stiamo finendo le domande da porre. Forse la casella di ricerca dovrebbe essere più grande del pulsante Chiedi domanda.

Vedi

+0

Non ho trovato una domanda che risponda a questo problema specifico. Puoi? – shoosh

+0

Non è perché si tratta di ereditarietà multipla che è possibile indovinare che è già stato risolto in altri post. Ha chiesto "Quello che sto cercando di fare è avere D in qualche modo elencare tutte le istanze di fn() che ha nella sua discendenza. Questo è possibile in qualche altro modo che funzioni virtuali? '.Anche se penso che sia stata una domanda un po 'ingenua, nessuna delle domande che hai collegato qui parla di una cosa del genere. Penso che fosse piuttosto specifico e unico nel suo interrogatorio. -1. –

4

Prima domanda, sì, B e C può definire fn() come una funzione virtuale. In secondo luogo, D può naturalmente accedere a B::fn() e C::fn() utilizzando l'operatore dell'oscilloscopio :: Terza domanda: D deve conoscere almeno B e C, poiché è necessario definirli nell'elenco di eredità. È possibile utilizzare i modelli di lasciare che i tipi di B e C aperti:

class A 
{ 
public: 
    virtual ~A() {} 
    virtual void fn() = 0; 
}; 

class B: public A 
{ 
public: 
    virtual ~B() {} 
    virtual void fn(){ std::cout << "B::fn()" << std::endl; } 
}; 

class C: public A 
{ 
public: 
    virtual ~C() {} 
    virtual void fn(){ std::cout << "C::fn()" << std::endl; } 
}; 

template <typename TypeB, typename TypeC> 
class D: public TypeB, public TypeC 
{ 
public: 
    void Do() 
    { 
     static_cast<TypeB*>(this)->fn(); 
     static_cast<TypeC*>(this)->fn(); 
    } 
}; 

typedef D<B, C> DInst; 

DInst d; 
d.Do(); 

Circa il desiderio di elencare automaticamente tutte le funzioni fn() di tutte le classi che eredita dalla D: Non sono sicuro se questo è possibile senza ricorrere a MPL. Almeno puoi estendere il mio esempio sopra con versioni che si occupano di 3 e più parametri del template, ma immagino ci sia un limite superiore (interno del compilatore) del numero di parametri del modello di classe.

+0

Stavo per pubblicare lo stesso. C'è un piccolo errore dato che D deve ereditare sia da B che da C e che non è nel codice sopra. Un'altra sintassi (più semplice del cast) sarebbe: TypeB :: fn() e TypeA :: fn() che sembrano più naturali. –

+0

Non sono sicuro che una chiamata TypeB :: fn() faccia la cosa giusta rispetto alla chiamata di funzione virtuale. Con il cast statico sei sicuro di avere un oggetto di tipo B. Immagino di doverlo provare. Grazie per la nota sulla correzione! – vividos

+1

Questa è probabilmente la soluzione più vicina a ciò di cui ho bisogno. Sfortunatamente nel mio caso il numero di classi nell'eredità (B, C) è anche variabile. Immagino che questo dovrà aspettare per gli argomenti del modello variabile del C++ 0x. – shoosh

1

Non è possibile enumerare le definizioni di fn() nella discendenza. Il C++ manca di riflessione. L'unico modo che posso immaginare è un enorme circuito che testa i tipi di tutti i possibili antenati. E fa male a immaginarlo.

0

Vividos ha già risposto alla parte principale del post. Anche se userei l'operatore di scope invece del più statico e statico operatore <> + dereferenziatore.

A seconda dell'operazione in corso, è possibile modificare la relazione di ereditarietà da D a B e C per una composizione di accoppiamento inferiore (più eventualmente l'ereditarietà da A). Ciò presuppone che non sia necessario che D sia usato polimorficamente come B o C e che non sia realmente necessario che B e C condividano la stessa istanza di base.

Se si sceglie la composizione, è possibile ricevere B e C come argomenti per il costruttore come riferimenti/puntatori di tipo A, rendendo D completamente ignaro dei tipi B e C. A quel punto, è possibile utilizzare un contenitore per contenere tanti oggetti derivati ​​da A. La tua implementazione di fn() (se così decidi) o qualsiasi altro metodo.

18

A meno che non si sovrascriva nuovamente fn in D, non è possibile. Perché non esiste un overrider finale in un oggetto D: Entrambi C e B ignorano A::fn. Sono disponibili diverse opzioni:

  • Eliminare C::fn o B::fn. Quindi, quello che sovrascrive lo A::fn ha lo scavalcamento finale.
  • Posizionare un overrider finale in D. Quindi, quello sovrascrive A::fn e fn in C e B.

Per esempio i seguenti risultati in un errore di tempo di compilazione:

#include <iostream> 

class A { 
public: 
    virtual void fn() { } 
}; 

class B : public virtual A { 
public: 
    virtual void fn() { } 
}; 

class C : public virtual A { 
public: 
    virtual void fn() { } 
}; 

// does not override fn!! 
class D : public B, public C { 
public: 
    virtual void doit() { 
     B::fn(); 
     C::fn(); 
    } 
}; 

int main(int argc, char **argv) { 
    D d; 
    d.doit(); 
    return 0; 
} 

È possibile, tuttavia derivare non virtuale da A in C e B, ma poi si deve più alcuna eredità diamante. Cioè, ogni membro di dati in A appare due volte in B e C perché due oggetti secondari di classe A di base in un oggetto D. Ti consiglierei di ripensare a quel disegno. Cerca di eliminare i doppi oggetti come quelli che richiedono l'ereditarietà virtuale. Spesso causa questo tipo di situazioni conflittuali.

Un caso molto simile a questo è quando si desidera eseguire l'override di una funzione specifica. Immagina di avere una funzione virtuale con lo stesso nome in B e C (ora senza una base comune A). E in D vuoi sovrascrivere ogni funzione, ma dare un comportamento diverso a ciascuna. A seconda che si chiami la funzione con un puntatore B o un puntatore C, si ha un comportamento diverso. Multiple Inheritance Part III di Herb Sutter descrive un buon modo per farlo. Potrebbe aiutarti a decidere sul tuo design.

+0

Penso che non sia quello che shoosh vuole fare. Ha affermato "Quello che sto cercando di fare è avere D in qualche modo elencare tutte le istanze di fn() che ha nella sua discendenza". Non vuole sovrascrivere fn() nella classe D. – vividos

+3

questa è la ragione per cui ho affermato che non è possibile. se C e B hanno la precedenza su A :: fn, allora non può definire D senza sovrascrivere fn in D –

+0

Cosa intendevi per "Drop"? – yanpas

1

Si potrebbe voler dare un'occhiata a Loki TypeLists se si ha realmente bisogno di essere in grado di tracciare le origini e di enumerare attraverso i tipi. Non sono sicuro che ciò che stai chiedendo sia davvero possibile senza un sacco di lavoro. Assicurati di non essere troppo ingegnerizzato qui.

Su una nota leggermente diversa, se si utilizzerà MI in questo modo (ad esempio, il temuto diamante), allora si dovrebbe essere molto espliciti su quale membro virtuale si desidera. Non riesco a pensare a un buon caso in cui si desidera scegliere la semantica di B::fn() su C::fn() senza prendere una decisione esplicita quando si scrive D. Probabilmente ne sceglierete uno sull'altro (o su entrambi) in base a ciò che fa il singolo metodo. Una volta presa una decisione, il requisito è che le modifiche ereditate non cambino le aspettative o l'interfaccia semantica.

Se siete davvero preoccupati per lo scambio di una nuova classe, dicono E al posto di dire B dove E non discende da B ma offre la stessa interfaccia, allora si dovrebbe davvero utilizzare l'approccio modello anche se non sono sicuro perché c'è un static_cast<> in là ...

struct A { 
    virtual ~A() {} 
    virtual void f() = 0; 
}; 
struct B: A { 
    virtual void f() { std::cout << "B::f()" << std::endl; } 
}; 
struct C: A { 
    virtual void f() { std::cout << "C::f()" << std::endl; } 
}; 

template <typename Base1, typename Base2> 
struct D: Base1, Base2 { 
    void g() { Base1::f(); Base2::f(); } 
}; 

int main() { 
    D<B,C> d1; 
    D<C,B> d2; 
    d1.g(); 
    d2.g(); 
    return 0; 
} 

// Outputs: 
// B::f() 
// C::f() 
// C::f() 
// B::f() 

funziona bene e sembra un po 'più facile da guardare.