In che modo C ++ gestisce l'ereditarietà multipla con un antenato comune condiviso?

13

Non sono un ragazzo C ++, ma sono costretto a pensarci. Perché l'ereditarietà multipla è possibile in C ++, ma non in C #? (Conosco il problema dei diamanti , ma non è quello che sto chiedendo qui). In che modo C ++ risolve l'ambiguità delle firme di metodo identiche ereditate da più classi di base? E perché lo stesso design non è incorporato in C #?

    
posta Sandeep 30.01.2013 - 06:17
fonte

2 risposte

24

Why is multiple inheritance possible in C++, but not in C#?

Penso (senza avere un riferimento difficile), che in Java volevano limitare l'espressività della lingua per rendere il linguaggio più facile da apprendere e perché il codice che utilizza l'ereditarietà multipla è più spesso troppo complesso per il suo bene. E poiché l'ereditarietà multipla completa è molto più complicata da implementare, quindi semplifica anche la macchina virtuale (l'ereditarietà multipla interagisce in modo particolare con il garbage collector, perché richiede il mantenimento dei puntatori nel mezzo dell'oggetto (all'inizio della base) )

E quando progettavo C # penso che guardassero a Java, ho visto che l'ereditarietà multipla non è stata trascurata e ha scelto di mantenere le cose semplici.

How does C++ resolve the ambiguity of identical method signatures inherited from multiple base classes?

È non . C'è una sintassi per chiamare esplicitamente il metodo della classe base dalla base specifica, ma non c'è modo di sovrascrivere solo uno dei metodi virtuali e se non si esegue l'override del metodo nella sottoclasse, non è possibile chiamarlo senza specificare la base classe.

And why is the same design not incorporated into C#?

Non c'è niente da incorporare.

Poiché Giorgio ha menzionato i metodi di estensione dell'interfaccia nei commenti, spiegherò quali sono i mixin e come sono implementati in varie lingue.

Le interfacce in Java e C # sono limitate ai soli metodi di dichiarazione. Ma i metodi devono essere implementati in ogni classe che eredita l'interfaccia. Esiste tuttavia un'ampia classe di interfacce, in cui sarebbe utile fornire implementazioni predefinite di alcuni metodi in termini di altri. Un esempio comune è comparabile (in pseudo-lingua):

mixin IComparable {
    public bool operator<(IComparable r) = 0;
    public bool operator>(IComparable r) { return r < this; }
    public bool operator<=(IComparable r) { return !(r < this); }
    public bool operator>=(IComparable r) { return !(r > this); }
    public bool operator==(IComparable r) { return !(r < this) && !(r > this); }
    public bool operator!=(IComparable r) { return r < this || r > this; }
};

La differenza dalla classe completa è che questo non può contenere alcun membro di dati. Ci sono diverse opzioni per l'implementazione di questo. Ovviamente l'ereditarietà multipla è una. Ma l'ereditarietà multipla è piuttosto complicata da implementare. Ma non è davvero necessario qui. Invece, molti linguaggi implementano questo dividendo il mixin in un'interfaccia, che è implementata dalla classe e un repository di implementazioni del metodo, che vengono iniettate nella classe stessa o una classe di base intermedia viene generata e vengono posizionate lì. Questo è implementato in Ruby e D , sarà implementato in Java 8 e può essere implementato manualmente in C ++ usando modello di modello ricorrente ricorrente . Quanto sopra, in forma CRTP, assomiglia a:

template <typename Derived>
class IComparable {
    const Derived &_d() const { return static_cast<const Derived &>(*this); }
public:
    bool operator>(const IComparable &r) const { r._d() < _d(); }
    bool operator<=(const IComparable &r) const { !(r._d() < _d(); }
    ...
};

e viene usato come:

class Concrete : public IComparable<Concrete> { ... };

Questo non richiede che nulla venga dichiarato virtuale come farebbe una normale classe base, quindi se l'interfaccia viene utilizzata nei modelli lascia aperte le opzioni di ottimizzazione utili. Nota che in C ++ questo probabilmente verrebbe comunque ereditato come secondo genitore, ma in lingue che non consentono l'ereditarietà multipla è inserito nella catena di ereditarietà singola, quindi è più simile a

template <typename Derived, typename Base>
class IComparable : public Base { ... };
class Concrete : public IComparable<Concrete, Base> { ... };

L'implementazione del compilatore può o non può evitare l'invio virtuale.

È stata selezionata una diversa implementazione in C #. In C # le implementazioni sono metodi statici di classe completamente separata e la sintassi della chiamata al metodo viene interpretata in modo appropriato dal compilatore se non esiste un metodo di nome specificato, ma viene definito un "metodo di estensione". Ciò ha il vantaggio che i metodi di estensione possono essere aggiunti alla classe già compilata e lo svantaggio che tali metodi non possono essere sostituiti ad es. per fornire una versione ottimizzata.

    
risposta data 30.01.2013 - 09:20
fonte
2

La risposta è che non funziona correttamente in C ++ in caso di conflitti nello spazio dei nomi. Vedi questo . Per evitare scontri nello spazio dei nomi devi fare tutti i tipi di rotazioni con i puntatori. Ho lavorato a MS nel team di Visual Studio e almeno in parte il motivo per cui hanno sviluppato la delega era di evitare del tutto la collisione nello spazio dei nomi. Preiouvsly avevo detto che consideravano anche le interfacce come parte della soluzione di ereditarietà multipla, ma mi sbagliavo. Le interfacce sono davvero straordinarie e possono essere fatte funzionare in C ++, FWIW.

La delega affronta in modo specifico la collisione nello spazio dei nomi: puoi delegare a 5 classi e tutte e 5 esporranno i loro metodi al tuo ambito come membri di prima classe. All'esterno guarda in questa IS l'ereditarietà multipla.

    
risposta data 30.01.2013 - 06:37
fonte

Leggi altre domande sui tag