Dlaczego wielokrotne dziedziczenie jest możliwe w C ++, ale nie w C #?
Myślę (bez twardego odniesienia), że w Javie chcieli ograniczyć ekspresję języka, aby uczynić go łatwiejszym do nauczenia się, a ponieważ kod wykorzystujący wielokrotne dziedziczenie jest często zbyt skomplikowany dla własnego dobra. A ponieważ pełne wielokrotne dziedziczenie jest o wiele bardziej skomplikowane do wdrożenia, dlatego też znacznie uprościło maszynę wirtualną (wielokrotne dziedziczenie szczególnie źle współdziała z modułem wyrzucania elementów bezużytecznych, ponieważ wymaga utrzymywania wskaźników na środku obiektu (na początku bazy) )
I projektując C #, myślę, że spojrzeli na Javę, zauważyli, że pełne wielokrotne dziedziczenie rzeczywiście nie było wiele pominięte i postanowili również uprościć sprawę.
Jak C ++ rozwiązuje niejednoznaczność identycznych podpisów metod odziedziczonych z wielu klas podstawowych?
Tak nie jest . Istnieje składnia umożliwiająca jawne wywołanie metody klasy bazowej z konkretnej bazy, ale nie ma sposobu, aby zastąpić tylko jedną z metod wirtualnych, a jeśli nie zastąpisz metody w podklasie, nie będzie możliwe jej wywołanie bez określenia bazy klasa.
I dlaczego ten sam projekt nie jest włączony do C #?
Nie ma nic do włączenia.
Ponieważ Giorgio wspominał o metodach rozszerzenia interfejsu w komentarzach, wyjaśnię, czym są miksy i jak są one implementowane w różnych językach.
Interfejsy w Javie i C # są ograniczone tylko do metod deklarowania. Ale metody muszą być zaimplementowane w każdej klasie, która dziedziczy interfejs. Istnieje jednak duża klasa interfejsów, w których przydatne byłoby zapewnienie domyślnych implementacji niektórych metod w odniesieniu do innych. Typowy przykład jest porównywalny (w pseudo-języku):
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; }
};
Różnica w stosunku do pełnej klasy polega na tym, że nie może zawierać żadnych elementów danych. Istnieje kilka opcji realizacji tego. Oczywiście wielokrotne dziedziczenie jest jednym. Ale wdrożenie wielokrotnego dziedziczenia jest raczej skomplikowane. Ale tutaj tak naprawdę nie jest to potrzebne. Zamiast tego wiele języków implementuje to, dzieląc mixin w interfejsie, który jest implementowany przez klasę i repozytorium implementacji metod, które są albo wstrzykiwane do samej klasy, albo generowana jest pośrednia klasa bazowa i tam umieszczane. Jest to zaimplementowane w Ruby i D , będzie zaimplementowane w Javie 8 i może być zaimplementowane ręcznie w C ++ przy użyciu ciekawie powtarzającego się wzorca szablonu . Powyższe w formie CRTP wygląda następująco:
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(); }
...
};
i jest używany jak:
class Concrete : public IComparable<Concrete> { ... };
Nie wymaga to deklarowania niczego wirtualnego, tak jak zrobiłaby to zwykła klasa podstawowa, więc jeśli interfejs jest używany w szablonach, pozostają użyteczne opcje optymalizacji otwarte. Zauważ, że w C ++ prawdopodobnie nadal byłby dziedziczony jako drugi element nadrzędny, ale w językach, które nie pozwalają na wielokrotne dziedziczenie, jest wstawiany do łańcucha pojedynczego dziedziczenia, więc bardziej przypomina
template <typename Derived, typename Base>
class IComparable : public Base { ... };
class Concrete : public IComparable<Concrete, Base> { ... };
Implementacja kompilatora może, ale nie musi, unikać wirtualnej wysyłki.
W C # wybrano inną implementację. W języku C # implementacje są metodami statycznymi całkowicie oddzielnej klasy, a składnia wywołania metody jest odpowiednio interpretowana przez kompilator, jeśli metoda o podanej nazwie nie istnieje, ale zdefiniowano „metodę rozszerzenia”. Ma to tę zaletę, że metody rozszerzeń można dodawać do już skompilowanej klasy, a wadą jest to, że takich metod nie można zastąpić, np. W celu zapewnienia zoptymalizowanej wersji.