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


13

Non sono un tipo 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à di firme di metodi identici ereditate da più classi di base? E perché lo stesso design non è incorporato in C #?


3
Per completezza, qual è il "problema dei diamanti", per favore?
jcolebrand,

1
@jcolebrand en.wikipedia.org/wiki/Multiple_inheritance see Diamond problem
l46kok

1
Certo, l'ho cercato su Google, ma come faccio a sapere che questo è ciò che Sandeep significa? Se hai intenzione di fare riferimento a qualcosa con un nome oscuro, quando "L'eredità multipla con un antenato comune condiviso" è più diretta ...
jcolebrand,

1
@jcolebrand L'ho modificato per riflettere ciò che ho ottenuto dalla domanda. Presumo che intenda il problema del diamante a cui fa riferimento wikipedia dal contesto. La prossima volta
lascia

1
@Earlz che funziona solo quando capisco il riferimento. Altrimenti sto facendo una brutta modifica, e questo è solo un brutto juju.
jcolebrand,

Risposte:


24

Perché l'ereditarietà multipla è possibile in C ++, ma non in C #?

Penso (senza avere un forte riferimento) che in Java volevano limitare l'espressività della lingua per rendere la lingua più facile da imparare 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 ha semplificato molto anche la macchina virtuale (l'ereditarietà multipla interagisce particolarmente male con Garbage Collector, poiché richiede di mantenere i puntatori nel mezzo dell'oggetto (all'inizio della base) )

E durante la progettazione di C # penso che abbiano guardato Java, visto che l'eredità multipla completa non è mancata molto ed è stata scelta per rendere le cose semplici.

In che modo C ++ risolve l'ambiguità di firme di metodi identici ereditate da più classi di base?

Lo fa non . Esiste una sintassi per chiamare esplicitamente il metodo della classe di base da una base specifica, ma non c'è modo di sovrascrivere solo uno dei metodi virtuali e se non si sostituisce il metodo nella sottoclasse, non è possibile chiamarlo senza specificare la base classe.

E perché lo stesso design non è incorporato in C #?

Non c'è niente da incorporare.


Poiché Giorgio ha menzionato i metodi di estensione dell'interfaccia nei commenti, spiegherò cosa 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. L'esempio comune è comparabile (in pseudo-linguaggio):

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 rispetto alla classe completa è che questo non può contenere alcun membro di dati. Esistono diverse opzioni per implementarlo. Ovviamente l'eredità multipla è una. Ma l'ereditarietà multipla è piuttosto complicata da implementare. Ma non è davvero necessario qui. Invece, molti linguaggi implementano ciò suddividendo il mixin in un'interfaccia, che è implementata dalla classe e da un repository di implementazioni di metodi, che vengono iniettati nella classe stessa o viene generata una classe base intermedia e vengono posizionati lì. Questo è implementato in Ruby e D , sarà implementato in Java 8 e può essere implementato manualmente in C ++ usando il modello di modello curiosamente 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(); }
    ...
};

ed è usato come:

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

Ciò non richiede nulla per essere dichiarato virtuale come farebbe una normale classe base, quindi se l'interfaccia utilizzata nei modelli lascia aperte utili opzioni di ottimizzazione. Si noti che in C ++ questo probabilmente sarebbe ancora ereditato come secondo genitore, ma in linguaggi che non consentono l'ereditarietà multipla è inserito nella catena dell'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 meno evitare l'invio virtuale.

Una diversa implementazione è stata selezionata in C #. In C # le implementazioni sono metodi statici di classe completamente separata e la sintassi della chiamata del metodo viene interpretata in modo appropriato dal compilatore se non esiste un metodo con un determinato nome, 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 esempio per fornire una versione ottimizzata.


Si potrebbe voler menzionare che l'ereditarietà multipla verrà introdotta in Java 8 con metodi di estensione dell'interfaccia.
Giorgio

@Giorgio: No, l'ereditarietà multipla non verrà sicuramente introdotta in Java. Lo saranno i mixin, il che è una cosa molto diversa, anche se copre molti dei motivi rimanenti per utilizzare l'ereditarietà multipla e la maggior parte dei motivi per utilizzare il modello di modello curiosamente ricorrente (CRTP) e funziona principalmente come CRTP, non come ereditarietà multipla.
Jan Hudec,

Penso che l'ereditarietà multipla non richieda puntatori nel mezzo di un oggetto. In tal caso, anche l'ereditarietà di più interfacce lo richiederebbe.
svick

@svick: No, non lo è. Ma l'alternativa è molto meno efficiente in quanto richiede l'invio virtuale per l'accesso dei membri.
Jan Hudec,

+1 per una risposta molto più completa della mia.
Nathan C. Tresch,

2

La risposta è che non funziona correttamente in C ++ in caso di conflitti nello spazio dei nomi. Vedere questo . Per evitare lo scontro dello spazio dei nomi devi fare tutti i tipi di giravolte con i puntatori. Ho lavorato presso 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 dello spazio dei nomi. Preiouvsly avevo detto che consideravano anche le interfacce parte della soluzione di ereditarietà multipla, ma mi sbagliavo. Le interfacce sono incredibilmente sorprendenti e possono essere fatte funzionare in C ++, FWIW.

La delega affronta in modo specifico la collisione dello spazio dei nomi: è possibile delegare a 5 classi e tutte e 5 esporteranno i loro metodi nel proprio ambito come membri di prima classe. All'esterno guardando in questa eredità multipla IS.


1
Trovo molto improbabile che questo sia stato il motivo principale per non includere l'MI in C #. Inoltre, questo post non risponde alla domanda sul perché MI funzioni in C ++ quando non si hanno "scontri nello spazio dei nomi".
Doc Brown,

@DocBrown Ho lavorato presso MS nel team di Visual Studio e ti assicuro che è almeno in parte il motivo per cui hanno sviluppato la delega e le interfacce, per evitare del tutto la collisione dello spazio dei nomi. Per quanto riguarda la qualità della domanda, meh. Usa il tuo voto negativo, altri sembrano pensare che sia utile.
Nathan C. Tresch,

1
Non ho intenzione di sottovalutare la tua risposta poiché penso che sia corretta. Ma la tua risposta finge che l'MI sia completamente inutilizzabile in C ++, ed è per questo che non l'hanno introdotta in C #. Anche se non mi piace molto l'MI in C ++, penso che non sia del tutto inammissibile.
Doc Brown,

1
È uno dei motivi e non intendevo implicare che non funzionasse mai. Quando qualcosa non è deterministico nell'informatica, tendo a dire che è "rotto", o che "non funziona" quando intendo dire "è come il voodoo, può o meno funzionare, accendere le candele e pregare". : D
Nathan C. Tresch,

1
le "collisioni dello spazio dei nomi" non sono deterministiche?
Doc Brown,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.