Как миксины или признаки лучше, чем обычное множественное наследование?

54

C ++ имеет простое множественное наследование, многие языковые схемы запрещают его как опасное. Но некоторые языки, такие как Ruby и PHP, используют странный синтаксис, чтобы делать то же самое и называть это миксинами или чертами. Я много раз слышал, что миксином / признаками сложнее злоупотреблять, чем простым множественным наследованием.

Что конкретно делает их менее опасными? Есть ли что-то, что невозможно с миксином / признаком, но возможно с множественным наследованием в стиле C ++? Можно ли столкнуться с проблемой алмазов с ними?

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

Gherman
источник
7
С нетерпением жду, когда кто-нибудь отправит хорошо написанный, вдумчивый и подробный ответ на этот вопрос.
Роберт Харви
7
Ruby и PHP не вводили миксины и черты. Миксины были введены во Flavors (1980), черты были введены в Squeak Smalltalk (2001). Flavors был самым первым объектно-ориентированным языком с множественным наследованием, и в нем использовались миксины. C ++ получил множественное наследование только в версии 2.0, выпущенной в 1989 году, спустя 9 лет после Flavors. Итак, вопрос должен быть следующим: Flavors имеет простые миксины, но некоторые языки, такие как C ++, вводят странный синтаксис, чтобы делать то же самое и называть это множественным наследованием.
Йорг Миттаг

Ответы:

31

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

Неоднозначность проявляется несколькими способами:

  1. Если у вас есть два базовых класса с одним xи тем же полем и запрашивается производный тип x, что он получает?
    • Если две xпеременные имеют несовпадающие типы, вы можете сделать это.
    • Если они одного типа, вы можете попытаться объединить их в одну переменную.
    • Вы всегда можете выставить их как странные полностью определенные имена.
  2. Если у вас есть два базовых класса с одной и той же функцией fс одинаковыми сигнатурами, и кто-то вызывает f, что вызывается ?
    • Что если два базовых класса имеют другого общего виртуального предка (проблема с бриллиантами).
    • Что если функция имеет разные, но совместимые подписи?
  3. Когда вы создаете класс с двумя базовыми классами, какой из конструкторов базовых классов вызывается первым? Когда вы уничтожаете объект, который убит?
  4. Когда вы размещаете объект в памяти, как вы делаете это последовательно?
  5. Как вы обрабатываете все эти случаи с 3 базовыми классами? 10?

И это игнорирует такие вещи, как динамическая диспетчеризация, вывод типов, сопоставление с образцом и другие вещи, о которых я знаю меньше, которые становятся более сложными, когда язык поддерживает множественное наследование полных классов.

Черты или Mix-In (или интерфейсы, или ...) - это все конструкции, которые специально ограничивают возможности типа, так что нет никакой двусмысленности. Они редко владеют чем-либо самим. Это позволяет сгладить состав типов, потому что нет двух переменных или двух функций ... есть переменная и ссылка; функция и подпись. Компилятор знает, что делать.

Другой распространенный подход - заставить пользователя «встраивать» (или смешивать) свой тип по одному. Вместо того, чтобы базовые классы были равноправными партнерами в новом типе, вы добавляете один тип к другому, переопределяя все, что там было (обычно с необязательным синтаксисом для переименования и / или повторного раскрытия переопределенных битов).

Есть ли что-то, что невозможно с миксином / признаком, но возможно с множественным наследованием в стиле C ++?

В зависимости от языка - обычно становится проблематичным или невозможным объединять реализации функций и хранилище для переменных из нескольких базовых классов и представлять их в производном типе.

Возможно ли столкнуться с проблемой алмазов с ними?

Иногда появляются менее серьезные изменения в зависимости от вашего языка, но обычно нет. Весь смысл черт состоит в том, чтобы сломать такую ​​двусмысленность.

Telastyn
источник
Можете ли вы дать мне пример языка / технологии, разрешающей такие "строительные" типы для экземпляра?
Герман
@german - я, конечно, не эксперт по Scala, но, насколько я понимаю, именно это и withделает ключевое слово.
Теластин
«конструкции, которые специально ограничивают возможности типа, чтобы не было двусмысленности»: какие ограничения? Интерфейсы не имеют переменных, так что это один предел, но есть черты Scala и модули Ruby. Они ограничены другими способами?
Дэвид Моулз
@DavidMoles - это распространенный способ, я также видел ограничения на правила виртуальной отправки (подумайте, явно реализованные интерфейсы C #).
Теластин