Как C ++ обрабатывает множественное наследование с общим общим предком?

13

Я не парень C ++, но я вынужден думать об этом. Почему множественное наследование возможно в C ++, но не в C #? (Я знаю о проблеме алмазов , но я не об этом здесь спрашиваю). Как C ++ разрешает неоднозначность идентичных сигнатур методов, унаследованных от нескольких базовых классов? И почему тот же дизайн не включен в C #?

Sandeep
источник
3
Ради полноты, в чем заключается "алмазная проблема", пожалуйста?
Jcolebrand
1
@jcolebrand en.wikipedia.org/wiki/Multiple_inheritance см. проблему с бриллиантами
l46kok
1
Конечно, я погуглил это, но откуда мне знать, что это то, что имеет в виду Сандип? Если вы собираетесь ссылаться на что-то по неясному имени, когда «множественное наследование с общим общим предком» более прямое ...
jcolebrand
1
@jcolebrand Я отредактировал это, чтобы отразить то, что я получил от вопроса. Я предполагаю, что он имеет в виду проблему алмазов, на которую ссылаются википедии из контекста. В следующий раз оставьте дружеский комментарий и используйте новый
великолепный
1
@Earlz, который работает только тогда, когда я понимаю ссылку. В противном случае я плохо редактирую, а это просто плохо.
Jcolebrand

Ответы:

24

Почему множественное наследование возможно в C ++, но не в C #?

Я думаю (не имея точных ссылок), что в Java они хотели ограничить выразительность языка, чтобы облегчить его изучение, и потому что код, использующий множественное наследование, чаще всего слишком сложен для своего блага, чем нет. А поскольку полное множественное наследование намного сложнее в реализации, оно значительно упростило и виртуальную машину (множественное наследование особенно плохо взаимодействует со сборщиком мусора, поскольку требует хранения указателей на середину объекта (в начале базы). )

И при разработке C # я думаю, что они смотрели на Java, увидели, что полное множественное наследование действительно не так уж и упущено, и решили сделать все проще.

Как C ++ разрешает неоднозначность идентичных сигнатур методов, унаследованных от нескольких базовых классов?

Это не так . Существует синтаксис для вызова метода базового класса из конкретной базы в явном виде, но нет способа переопределить только один из виртуальных методов, и если вы не переопределите метод в подклассе, его невозможно вызвать без указания базы учебный класс.

И почему тот же дизайн не включен в C #?

Там нет ничего, чтобы включить.


Поскольку Джорджио упомянул методы расширения интерфейса в комментариях, я объясню, что такое миксины и как они реализованы на разных языках.

Интерфейсы в Java и C # ограничены только объявлением методов. Но методы должны быть реализованы в каждом классе, который наследует интерфейс. Однако существует большой класс интерфейсов, где было бы полезно предоставить реализации некоторых методов по умолчанию в терминах других. Типичный пример сопоставим (на псевдо-языке):

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; }
};

Отличие от полного класса в том, что он не может содержать никаких элементов данных. Есть несколько вариантов реализации этого. Очевидно, множественное наследование одно. Но множественное наследование довольно сложно реализовать. Но это действительно не нужно здесь. Вместо этого многие языки реализуют это, разделяя миксин в интерфейсе, который реализуется классом, и в хранилище реализаций методов, которые либо внедряются в сам класс, либо генерируется промежуточный базовый класс, и они размещаются там. Это реализовано в Ruby и D , будет реализовано в Java 8 и может быть реализовано вручную в C ++ с использованием любопытно повторяющегося шаблона . Выше, в форме CRTP, выглядит так:

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(); }
    ...
};

и используется как:

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

Для этого не требуется ничего объявлять виртуальным, как это делает обычный базовый класс, поэтому, если интерфейс используется в шаблонах, остаются открытыми полезные параметры оптимизации. Обратите внимание, что в C ++ это, вероятно, все еще будет наследоваться как второй родитель, но в языках, которые не допускают множественное наследование, оно вставляется в единую цепочку наследования, так что это больше похоже на

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

Реализация компилятора может или не может избежать виртуальной диспетчеризации.

В C # была выбрана другая реализация. В C # реализации являются статическими методами совершенно отдельного класса, и синтаксис вызова метода соответствующим образом интерпретируется компилятором, если метод с данным именем не существует, но определен «метод расширения». Это имеет преимущество в том, что методы расширения могут быть добавлены к уже скомпилированному классу, и недостаток в том, что такие методы не могут быть переопределены, например, для обеспечения оптимизированной версии.

Ян Худек
источник
Можно упомянуть, что множественное наследование будет введено в Java 8 с помощью методов расширения интерфейса.
Джорджио
@ Джорджио: Нет, множественное наследование определенно не будет введено в Java. Будут миксины, что совсем другое дело, хотя оно охватывает множество оставшихся причин для использования множественного наследования и большинство причин для использования любопытно повторяющегося шаблона (CRTP) и работает в основном как CRTP, а не как множественное наследование вообще.
Ян Худек
Я думаю, что множественное наследование не требует указателей на середину объекта. Если бы это произошло, множественное наследование интерфейса также потребовало бы этого.
svick
@svick: Нет, это не так. Но альтернатива гораздо менее эффективна, так как требует виртуальной отправки для доступа членов.
Ян Худек
+1 за гораздо более полный ответ, чем мой.
Натан Треш
2

Ответ заключается в том, что он не работает правильно в C ++ в случае столкновений пространства имен. Смотрите это . Чтобы избежать столкновения пространства имен, вы должны выполнять все виды вращений с указателями. Я работал в MS в команде Visual Studio и, по крайней мере, отчасти потому, что они разработали делегирование, заключались в том, чтобы вообще избежать коллизий пространства имен. Ранее я говорил, что они также считают интерфейсы частью решения множественного наследования, но я ошибся. Интерфейсы действительно удивительны и могут работать на C ++, FWIW.

В частности, делегирование устраняет коллизию пространства имен: вы можете делегировать до 5 классов, и все 5 из них будут экспортировать свои методы в вашу область как члены первого класса. Наружный взгляд на это - множественное наследование.

Натан Треш
источник
1
Я считаю очень маловероятным, что это было основной причиной не включать MI в C #. Кроме того, этот пост не отвечает на вопрос, почему MI работает в C ++, когда у вас нет «столкновений пространства имен».
Док Браун
@DocBrown Я работал в MS в команде Visual Studio и, уверяю вас, это хотя бы отчасти причина, по которой они разработали делегирование и интерфейсы, чтобы вообще избежать коллизий пространства имен. Что касается качества вопроса, мех. Используйте свое отрицательное мнение, другие, кажется, думают, что это полезно.
Натан С. Треш
1
Я не собираюсь опровергать ваш ответ, поскольку считаю его правильным. Но ваш ответ делает вид, что MI совершенно непригоден для C ++, и по этой причине они не представили его в C #. Хотя мне не очень нравится MI в C ++, я думаю, что это не совсем невозможно.
Док Браун
1
Это одна из причин, и я не хотел подразумевать, что это никогда не работает. Когда что-то недетерминировано в вычислениях, я склонен говорить, что оно «сломано» или что оно «не работает», когда я имею в виду «это как вуду, оно может работать или не работать, зажигать свечи и молиться». : D
Натан С. Треш
1
"столкновения пространства имен" недетерминированы?
Док Браун