2012-01-02 14 views
16

Il seguente codice:ereditarietà multipla e puri funzioni virtuali

struct interface_base 
{ 
    virtual void foo() = 0; 
}; 

struct interface : public interface_base 
{ 
    virtual void bar() = 0; 
}; 

struct implementation_base : public interface_base 
{ 
    void foo(); 
}; 

struct implementation : public implementation_base, public interface 
{ 
    void bar(); 
}; 

int main() 
{ 
    implementation x; 
} 

non riesce a compilare con i seguenti errori:

test.cpp: In function 'int main()': 
test.cpp:23:20: error: cannot declare variable 'x' to be of abstract type 'implementation' 
test.cpp:16:8: note: because the following virtual functions are pure within 'implementation': 
test.cpp:3:18: note: virtual void interface_base::foo() 

ho giocato intorno con esso e capito che, per rendere l' 'interfaccia - > interface_base 'e' implementation_base -> interface_base 'ereditarietà virtuale, risolve il problema, ma non capisco perché. Qualcuno può spiegare cosa sta succedendo?

p.s. Ho omesso di proposito i distruttori virtuali per rendere il codice più breve. Si prega di non dirmi di metterli in, so già :)

+0

vedere anche: http://stackoverflow.com/questions/457609/diamond-inheritance-and-pure-virtual-functions – bdonlan

risposta

14

Hai dueinterface_base classi di base nel vostro albero di ereditarietà. Ciò significa che è necessario fornire due implementazioni di foo(). E chiamare uno di questi sarà davvero imbarazzante, richiedendo più cast per disambiguare. Questo di solito non è quello che vuoi.

Per risolvere questo, utilizzare l'ereditarietà virtuale:

struct interface_base 
{ 
    virtual void foo() = 0; 
}; 

struct interface : virtual public interface_base 
{ 
    virtual void bar() = 0; 
}; 

struct implementation_base : virtual public interface_base 
{ 
    void foo(); 
}; 

struct implementation : public implementation_base, virtual public interface 
{ 
    void bar(); 
}; 

int main() 
{ 
    implementation x; 
} 

con l'ereditarietà virtuale, una sola istanza della classe base in questione è creato nella gerarchia eredità per menzioni tutto virtuale. Quindi, c'è solo uno foo(), che può essere soddisfatto da implementation_base::foo().

Per ulteriori informazioni, see this prior question - le risposte forniscono alcune belle diagrammi per rendere tutto questo più chiaro.

+0

+1: Beat me ad esso. –

+0

C'è un modo per dire "usa l'implementazione da' implementation_base' "per entrambe le implementazioni di' foo() ', senza usare l'ereditarietà virtuale? – HighCommander4

+0

@ HighCommander4, è possibile derivare l'interfaccia da 'implementation_base' o viceversa, eliminando così il diamante e creando un albero di ereditarietà lineare. A parte questo, no, ma se vuoi nasconderlo, potresti creare involucri vuoti 'interface_base' e' interface' che virtualmente derivano dalla classe _real_ dell'interfaccia. – bdonlan

2

Per il caso di 'risolvere' il problema di diamante eredità, le soluzioni offerte da bdonlan sono validi. Detto questo, puoi evitare il problema del diamante con il design. Perché ogni istanza di una data classe deve essere vista come entrambe le classi? Stai andando mai a passare questo stesso oggetto di una classe che dice qualcosa del tipo:

void ConsumeFood(Food *food); 
void ConsumeDrink(Drink *drink); 

class NutritionalConsumable { 
    float calories() = 0; 
    float GetNutritionalValue(NUTRITION_ID nutrition) = 0; 
}; 
class Drink : public NutritionalConsumable { 
    void Sip() = 0; 
}; 
class Food : public NutritionalConsumable { 
    void Chew() = 0; 
}; 
class Icecream : public Drink, virtual public Food {}; 

void ConsumeNutrition(NutritionalConsumable *consumable) { 
    ConsumeFood(dynamic_cast<Food*>(food)); 
    ConsumeDrink(dynamic_cast<Drink*>(drink)); 
} 

// Or moreso 
void ConsumeIcecream(Icecream *icecream) { 
    ConsumeDrink(icecream); 
    ConsumeFood(icecream); 
} 

Sicuramente sarebbe meglio in questo caso per Icecream da implementare solo NutritionalConsumable e fornire un metodo GetAsDrink() e GetAsFood() che restituirà un proxy , puramente per il gusto di apparire come cibo o bevanda. Altrimenti che suggerisce che v'è un metodo o un oggetto che accetta un Food ma in qualche modo vuole vedere in seguito come un Drink, che può essere raggiunto solo con un dynamic_cast, e non deve essere il caso con un design più appropriato.

+1

Il tuo esempio è piuttosto diverso dal mio.Nel mio caso, 'implementation' deriva da' interface' perché sarà usato polimorficamente come 'interface *', e deriva da 'implementation_base' per riutilizzare l'implementazione della porzione' interface_base' di 'interface'. – HighCommander4

+1

Ok, quindi chi ha bisogno di vederlo come un 'implementation_base'? E chi comunica con 'interface' ma non' interface_base'? Qual è stato il pensiero dietro 'implementation_base' ereditato da' interface_base' piuttosto che 'interface'? Sebbene le mie domande siano piuttosto astratte, stai risolvendo un vero design di sistema o solo il problema dei diamanti? –

+0

Nessuno ha bisogno di vederlo come 'implementation_base', potrei averlo appena derivato da' interface' e duplicato l'implementazione all'interno di 'implementation_base' - ma non voglio duplicare le cose. – HighCommander4

8

L'++ linguaggio solito C è:

  • pubblico virtuale eredità per interfaccia classi
  • privato eredità non virtuale per implementazione classi

I n questo caso avremmo:

struct interface_base 
{ 
    virtual void foo() = 0; 
}; 

struct interface : virtual public interface_base 
{ 
    virtual void bar() = 0; 
}; 

struct implementation_base : virtual public interface_base 
{ 
    void foo(); 
}; 

struct implementation : private implementation_base, 
         virtual public interface 
{ 
    void bar(); 
}; 

Nel implementation, l'unica interface_base base virtuale è:

  • ereditato pubblicamente via interface: implementation --public ->interface --public ->interface_base
  • ereditato privatamente via implementation_base: implementation --private ->implementation_base --public ->interface_base

Quando il codice client fa uno di questi derivati ​​di conversioni di base:

  • derivata conversioni puntatore base,
  • riferimento legame di tipo base con un inizializzatore di tipo statico derivato,
  • accesso ai ereditato membri della classe base tramite un lvalue di tipo statico, derivato

quello che conta è solo che c'è un almeno un accessibl e il percorso di ereditarietà dalla classe derivata al dato subobject della classe base; altri percorsi inaccessibili sono semplicemente ignorati. Poiché l'ereditarietà della classe base è solo virtuale qui, c'è solo un oggetto di classe base, quindi queste conversioni non sono mai ambigue.

Qui, la conversione da implementation a interface_base, può sempre essere eseguita dal codice cliente tramite interface; l'altro percorso inaccessibile non ha alcuna importanza. L'esclusiva base virtuale interface_base è ereditata pubblicamente da implementation.

In molti casi, le classi di implementazione (implementation, implementation_base) saranno tenute nascoste dal codice cliente: saranno esposti solo i puntatori o riferimenti alle classi di interfaccia (interface, interface_base).

+0

Non direi che" l'ereditarietà virtuale pubblica per le classi di interfaccia "è" il (o un) idioma C++ usuale ". Forse dovrebbe essere, ma da quello che posso dire sicuramente non lo è. –

+0

@PaulGroke Hum .. potresti avere ragione. Idioma corretto? idioma consigliato? – curiousguy

+0

Ho scritto "forse dovrebbe essere", perché onestamente non sono sicuro se dovrebbe. Non utilizzo quasi mai l'ereditarietà virtuale (in effetti non sono sicuro che l'ho * mai * utilizzata nel codice di produzione). Quindi non conosco i problemi che può causare e quali di questi problemi influenzano anche l'ereditarietà virtuale con classi di interfaccia pure (ad esempio trivial ctor, trivial dtor, no data). –