В чем именно заключается проблема множественного наследования?

Я вижу, как люди все время спрашивают, следует 9X_oo ли включать множественное наследование в 9X_oo-design следующую версию C# или Java. Специалисты 9X_ood по C++, которым посчастливилось обладать 9X_oo-design этой способностью, говорят, что это все 9X_oo равно что дать кому-то веревку, чтобы он 9X_oop в конце концов повесился.

Что случилось с 9X_oops множественным наследованием? Есть какие-нибудь 9X_language-theory конкретные образцы?

131
4

  • Я думал, что C++ дает достаточно веревки, чтобы прострелить себе ногу.<p><spa ...
11
Общее количество ответов: 11

Ответ #1

Ответ на вопрос: В чем именно заключается проблема множественного наследования?

Самая очевидная проблема связана с переопределением 9X_oo функции.

Допустим, есть два класса A и B, каждый 9X_oo-design из которых определяет метод doSomething. Теперь вы 9X_multiple-inheritance определяете третий класс C, который наследуется 9X_ood как от A, так и от B, но вы не переопределяете 9X_object-oriented метод doSomething.

Когда компилятор заполняет этот код 9X_object-orientation ...

C c = new C(); c.doSomething(); 

... какую реализацию метода следует использовать? Без 9X_object-oriented-modeling дополнительных разъяснений компилятор не 9X_object-oriented-design может разрешить двусмысленность.

Помимо переопределения, другой 9X_ood большой проблемой множественного наследования 9X_oo является расположение физических объектов 9X_object-oriented в памяти.

Такие языки, как C++, Java и C#, создают 9X_oo-design фиксированный адресный макет для каждого 9X_object-orientation типа объекта. Примерно так:

class A: at offset 0 ... "abc" ... 4 byte int field at offset 4 ... "xyz" ... 8 byte double field at offset 12 ... "speak" ... 4 byte function pointer class B: at offset 0 ... "foo" ... 2 byte short field at offset 2 ... 2 bytes of alignment padding at offset 4 ... "bar" ... 4 byte array pointer at offset 8 ... "baz" ... 4 byte function pointer 

Когда компилятор 9X_oops генерирует машинный код (или байт-код), он 9X_object-orientation использует эти числовые смещения для доступа 9X_object-oriented к каждому методу или полю.

Множественное 9X_oo наследование усложняет задачу.

Если класс 9X_object-oriented-modeling C наследуется как от A, так и от B, компилятор 9X_ood должен решить, размещать ли данные в порядке 9X_multiple-inheritance AB или в порядке BA.

Но теперь представьте, что 9X_language-theory вы вызываете методы для объекта B. Неужели 9X_oo это просто B? Или это действительно объект 9X_oo-design C, вызываемый полиморфно через его интерфейс 9X_multiple-inheritance B? В зависимости от фактической идентичности 9X_object-oriented-modeling объекта физическая структура будет отличаться, и 9X_language-theory невозможно узнать смещение функции, вызываемой 9X_ood на сайте вызова.

Чтобы справиться с такой 9X_object-oriented-design системой, нужно отказаться от подхода с 9X_multiple-inheritance фиксированным макетом, позволяя запрашивать 9X_oo-design макет каждого объекта перед попыткой вызвать 9X_object-oriented функции или получить доступ к его полям.

Итак 9X_language-theory ... короче ... авторам компиляторов сложно 9X_object-oriented поддерживать множественное наследование. Поэтому, когда 9X_object-oriented-modeling кто-то вроде Гвидо ван Россума разрабатывает 9X_object-oriented-modeling python, или когда Андерс Хейлсберг разрабатывает 9X_oo-design C#, они знают, что поддержка множественного 9X_oop наследования значительно усложняет реализацию 9X_oo компилятора, и, по-видимому, они не думают, что 9X_oop выгода стоит затрат.

97
6

  • Я думаю, что OP спрашивал о MI с точки зрения пользователя (программиста), а не с точки зрения разработ ...

Ответ #2

Ответ на вопрос: В чем именно заключается проблема множественного наследования?

Проблемы, о которых вы упомянули, на самом 9X_object-oriented-design деле не так уж и сложно решить. Фактически, например, Эйфель 9X_oo прекрасно это делает! (и без введения произвольного 9X_object-orientation выбора или чего-то еще)

Например, если вы 9X_multiple-inheritance наследуете от A и B, оба из которых имеют 9X_oo метод foo(), то, конечно, вы не хотите, чтобы 9X_oo произвольный выбор в вашем классе C унаследовал 9X_ood от A и B. Вам нужно либо переопределить 9X_language-theory foo, чтобы было ясно, что будет использоваться 9X_ood при вызове c.foo(), либо в противном случае 9X_oops вам придется переименовать один из методов 9X_object-oriented-design в C. (он может стать bar())

Также я считаю, что 9X_multiple-inheritance множественное наследование часто бывает 9X_language-theory весьма полезным. Если вы посмотрите библиотеки 9X_object-oriented Eiffel, вы увидите, что он используется 9X_ood повсеместно, и лично я пропустил эту функцию, когда 9X_object-oriented-design мне пришлось вернуться к программированию 9X_object-oriented на Java.

50
10

  • Из любопытства, по каким языкам можно судить о МИ?< ...

Ответ #3

Ответ на вопрос: В чем именно заключается проблема множественного наследования?

The diamond problem:

двусмысленность, которая возникает, когда 9X_ood два класса B и C наследуются от A, а класс 9X_object-orientation D наследуется от B и C. Если в A есть метод, который 9X_object-oriented-modeling B и C имеют overridden, и D не переопределяет его, тогда 9X_object-oriented-design какую версию метода наследует D: версию 9X_multiple-inheritance B или версию C?

... Это называется "ромбовидной 9X_object-oriented-design проблемой" из-за формы диаграммы наследования 9X_language-theory классов в этой ситуации. В этом случае класс 9X_oo A находится наверху, B и C по отдельности 9X_object-oriented-design под ним, а D соединяет их вместе внизу, образуя 9X_oop ромбовидную форму ...

28
4

  • @IanGoldby: Достаточно честно. Я хотел сказать, что разработчики Ja ...

Ответ #4

Ответ на вопрос: В чем именно заключается проблема множественного наследования?

Множественное наследование - одна из тех 9X_object-oriented-modeling вещей, которые используются не часто и могут 9X_oo использоваться неправильно, но иногда необходимы.

Я 9X_object-oriented-modeling никогда не понимал, что нельзя не добавлять 9X_object-oriented-design функцию только потому, что она может быть 9X_multiple-inheritance использована неправильно, когда нет хороших 9X_multiple-inheritance альтернатив. Интерфейсы не являются альтернативой 9X_object-oriented-modeling множественному наследованию. Во-первых, они 9X_multiple-inheritance не позволяют вам применять предусловия или 9X_oops постусловия. Как и любой другой инструмент, вам 9X_oops нужно знать, когда он уместен и как его 9X_oo использовать.

22
2

  • @curiousguy: вы используете язык с подходящим синтаксисом, который позволяет вам помещать предварительные и постусловия непосредственно в интерфейс: никаких "утверждений" не требуется. Пример от Феликса: fun div (num: int, d ...

Ответ #5

Ответ на вопрос: В чем именно заключается проблема множественного наследования?

предположим, что у вас есть объекты A и 9X_oo-design B, которые наследуются C. A и B оба реализуют 9X_oo foo(), а C - нет. Я вызываю C.foo(). Какая 9X_oop реализация будет выбрана? Есть и другие 9X_oops проблемы, но они очень серьезные.

17
2

  • Но это не совсем конкретный пример. Если и у A, и у B есть функция, весьма вероятно, что C также по ...

Ответ #6

Ответ на вопрос: В чем именно заключается проблема множественного наследования?

Я не думаю, что проблема с бриллиантами 9X_language-theory является проблемой, я считаю эту софистику, и 9X_object-orientation ничего больше.

Наихудшая проблема, с моей 9X_oop точки зрения, с множественным наследованием 9X_oo-design - это RAD - жертвы и люди, которые утверждают, что 9X_object-oriented они разработчики, но на самом деле застряли 9X_multiple-inheritance на полузнании (в лучшем случае).

Лично я 9X_oo-design был бы очень рад, если бы наконец смог сделать 9X_oo что-то подобное в Windows Forms (это неправильный 9X_object-oriented-modeling код, но он должен дать вам идею):

public sealed class CustomerEditView : Form, MVCView 

Это основная 9X_language-theory проблема, с которой я столкнулся с отсутствием 9X_object-orientation множественного наследования. Вы МОЖЕТЕ сделать 9X_object-oriented-design что-то подобное с интерфейсами, но есть 9X_oo то, что я называю «дерьмовым кодом», это 9X_oops болезненный повторяющийся треп, который 9X_oops вам нужно написать в каждом из ваших классов, например, для 9X_multiple-inheritance получения контекста данных.

На мой взгляд, в 9X_oo ЛЮБОМ повторении кода на современном языке 9X_language-theory не должно быть абсолютно никакой необходимости.

7
1

  • @Turing Complete: без повторения кода: это хорошая идея, но это неверно и невозможно. Существует огромно ...

Ответ #7

Ответ на вопрос: В чем именно заключается проблема множественного наследования?

Основная проблема множественного наследования 9X_ood хорошо описана на примере tloach. При наследовании 9X_object-oriented-design от нескольких базовых классов, реализующих 9X_oops одну и ту же функцию или поле, компилятор 9X_oo должен принять решение о том, какую реализацию 9X_oop унаследовать.

Это становится еще хуже, когда 9X_object-oriented вы наследуете от нескольких классов, которые 9X_language-theory наследуются от одного и того же базового 9X_multiple-inheritance класса. (ромбовидное наследование, если 9X_object-oriented-design вы нарисуете дерево наследования, вы получите 9X_object-oriented-modeling ромбовидную форму)

На самом деле компилятору 9X_multiple-inheritance нетрудно решить эти проблемы. Но выбор, который 9X_object-oriented должен сделать здесь компилятор, довольно 9X_object-orientation произвольный, что делает код менее интуитивно 9X_object-oriented понятным.

Я считаю, что при хорошем объектно-ориентированном 9X_oo-design дизайне мне никогда не понадобится множественное 9X_oop наследование. В тех случаях, когда мне это 9X_oops действительно нужно, я обычно обнаруживаю, что 9X_oops использую наследование для повторного использования 9X_object-oriented-modeling функциональных возможностей, в то время 9X_ood как наследование подходит только для отношений 9X_object-orientation «is-a».

Существуют и другие методы, например 9X_oop миксины, которые решают те же проблемы и 9X_oop не имеют проблем, присущих множественному 9X_oop наследованию.

5
2

  • Скомпилированному * не * нужно делать произвольный выбор - он может просто выйти из строя. В C#, что это за тип `([..bool ..]?" Test ...

Ответ #8

Ответ на вопрос: В чем именно заключается проблема множественного наследования?

В самом множественном наследовании нет ничего 9X_oo плохого. Проблема состоит в том, чтобы добавить 9X_object-oriented множественное наследование к языку, который 9X_oo-design изначально не был разработан с учетом множественного 9X_object-oriented-modeling наследования.

Язык Eiffel поддерживает множественное 9X_object-orientation наследование без ограничений очень эффективным 9X_oo и продуктивным способом, но с самого начала 9X_oop язык был разработан для его поддержки.

Эту 9X_oops функцию сложно реализовать разработчикам 9X_oo компиляторов, но кажется, что этот недостаток 9X_multiple-inheritance может быть компенсирован тем фактом, что 9X_multiple-inheritance хорошая поддержка множественного наследования 9X_object-oriented-modeling могла бы избежать поддержки других функций 9X_object-oriented-modeling (то есть не требуется интерфейс или метод 9X_object-oriented-modeling расширения).

Я думаю, что поддержка множественного 9X_object-orientation наследования или нет - это скорее вопрос 9X_object-oriented-design выбора, вопрос приоритетов. Более сложная 9X_oops функция требует больше времени для правильной 9X_object-oriented реализации и эксплуатации и может вызвать 9X_ood больше споров. Реализация C++ может быть 9X_ood причиной того, почему множественное наследование 9X_oop не было реализовано в C# и Java ...

3
4

  • Компиляторы Eiffel должны провести глобальный программный анализ, чтобы эффекти ...

Ответ #9

Ответ на вопрос: В чем именно заключается проблема множественного наследования?

Common Lisp Object System (CLOS) - еще один 9X_object-oriented-modeling пример чего-то, что поддерживает MI, избегая 9X_multiple-inheritance при этом проблем в стиле C++: наследованию 9X_oo присваивается sensible default, при этом вы по-прежнему 9X_multiple-inheritance можете свободно решать, как именно, скажем, назвать 9X_object-orientation поведение супергероя.

3
0

Ответ #10

Ответ на вопрос: В чем именно заключается проблема множественного наследования?

Ромб не является проблемой, если вы не используете 9X_oo-design что-либо вроде виртуального наследования 9X_oo-design C++: при обычном наследовании каждый базовый 9X_language-theory класс напоминает поле-член (на самом деле 9X_object-oriented-modeling они размещены в ОЗУ в этом способ), что 9X_ood дает вам синтаксический сахар и дополнительную 9X_multiple-inheritance возможность переопределить больше виртуальных 9X_oo-design методов. Это может вызвать некоторую двусмысленность 9X_oo-design во время компиляции, но обычно это легко 9X_object-oriented решить.

С другой стороны, виртуальное наследование 9X_oo-design слишком легко выходит из-под контроля (а 9X_oo затем превращается в беспорядок). Рассмотрим 9X_object-oriented-design в качестве примера диаграмму «сердце»:

 A A / \ / \ B C D E \ / \ / F G \ / H 

В 9X_oop C++ это совершенно невозможно: как только 9X_multiple-inheritance F и G объединяются в один класс, их A также 9X_oo-design объединяются, точка. Это означает, что вы 9X_object-orientation никогда не можете считать базовые классы 9X_object-oriented непрозрачными в C++ (в этом примере вам 9X_object-oriented-design нужно создать A в H, чтобы вы знали, что он 9X_oo присутствует где-то в иерархии). Однако 9X_oops на других языках это может работать; например, F и 9X_oo G могут явно объявить A как «внутреннее», тем 9X_oo-design самым запретив последующее слияние и эффективно 9X_object-oriented-modeling сделав себя целостными.

Еще один интересный 9X_oo пример (не специфичный для C++):

 A / \ B B | | C D \ / E 

Здесь только 9X_multiple-inheritance B использует виртуальное наследование. Итак, E содержит 9X_object-orientation два элемента B, которые используют один и 9X_oops тот же A. Таким образом, вы можете получить 9X_multiple-inheritance указатель A*, который указывает на E, но вы 9X_oo не можете преобразовать его в указатель 9X_oops B*, хотя объект на самом деле B, поскольку такое приведение 9X_object-oriented-modeling неоднозначно, и эта неоднозначность не может 9X_oop быть обнаружена во время компиляции (если 9X_object-orientation компилятор не видит всю программу). Вот 9X_oops тестовый код:

struct A { virtual ~A() {} /* so that the class is polymorphic */ }; struct B: virtual A {}; struct C: B {}; struct D: B {}; struct E: C, D {}; int main() { E data; E *e = &data; A *a = dynamic_cast(e); // works, A is unambiguous // B *b = dynamic_cast(e); // doesn't compile B *b = dynamic_cast(a); // NULL: B is ambiguous std::cout << "E: " << e << std::endl; std::cout << "A: " << a << std::endl; std::cout << "B: " << b << std::endl; // the next casts work std::cout << "A::C::B: " << dynamic_cast(dynamic_cast(e)) << std::endl; std::cout << "A::D::B: " << dynamic_cast(dynamic_cast(e)) << std::endl; std::cout << "A=>C=>B: " << dynamic_cast(dynamic_cast(a)) << std::endl; std::cout << "A=>D=>B: " << dynamic_cast(dynamic_cast(a)) << std::endl; return 0; } 

Более того, реализация может 9X_object-oriented быть очень сложной (зависит от языка; см. ответ 9X_ood Бенджисмита).

3
0

Ответ #11

Ответ на вопрос: В чем именно заключается проблема множественного наследования?

Одна из целей разработки таких фреймворков, как 9X_object-oriented Java и .NET, состоит в том, чтобы позволить 9X_multiple-inheritance скомпилированному коду работать с одной 9X_multiple-inheritance версией предварительно скомпилированной 9X_language-theory библиотеки, чтобы он работал одинаково хорошо 9X_oop с последующими версиями этой библиотеки, даже 9X_object-oriented если эти последующие версии добавляют новые 9X_language-theory функции. В то время как нормальная парадигма 9X_object-oriented-modeling в таких языках, как C или C++, заключается 9X_multiple-inheritance в распространении статически связанных исполняемых 9X_object-oriented-design файлов, содержащих все необходимые библиотеки, парадигма 9X_oo-design в .NET и Java заключается в распространении 9X_object-oriented приложений в виде наборов компонентов, которые 9X_object-orientation «связаны» во время выполнения. .

Модель COM, предшествовавшая 9X_oops .NET, пыталась использовать этот общий подход, но 9X_oop на самом деле у нее не было наследования 9X_ood - вместо этого каждое определение класса 9X_oop эффективно определяло как класс, так и интерфейс 9X_ood с тем же именем, который содержал все его 9X_multiple-inheritance общедоступные члены. . Экземпляры относились 9X_ood к типу класса, а ссылки - к типу интерфейса. Объявление 9X_language-theory класса производным от другого было эквивалентно 9X_object-oriented объявлению класса как реализующего интерфейс 9X_oo другого, и требовало, чтобы новый класс 9X_ood повторно реализовал все общедоступные члены 9X_object-oriented-modeling классов, от которых один является производным. Если 9X_oops Y и Z происходят от X, а затем W происходит 9X_language-theory от Y и Z, не имеет значения, реализуют ли 9X_object-oriented-modeling Y и Z члены X по-разному, потому что Z не 9X_multiple-inheritance сможет использовать их реализации - ему 9X_object-oriented-modeling нужно будет определить свои собственный. W 9X_oops может инкапсулировать экземпляры Y и / или 9X_oo-design Z и связать свои реализации методов X через 9X_ood их методы, но не будет двусмысленности относительно 9X_ood того, что должны делать методы X - они будут 9X_oo-design делать то, что код Z явно предписывал им 9X_object-oriented-design делать.

Сложность Java и .NET заключается 9X_language-theory в том, что коду разрешено наследовать члены 9X_object-orientation и иметь доступ к ним, неявно ссылаясь на родительские 9X_object-orientation члены. Предположим, у кого-то есть классы 9X_oo-design W-Z, связанные, как указано выше:

class X { public virtual void Foo() { Console.WriteLine("XFoo"); } class Y : X {}; class Z : X {}; class W : Y, Z // Not actually permitted in C# { public static void Test() { var it = new W(); it.Foo(); } } 

Казалось 9X_oo-design бы, W.Test() должен создать экземпляр W для вызова 9X_object-oriented-design реализации виртуального метода Foo, определенного 9X_object-oriented в X. Однако предположим, что Y и Z на самом 9X_object-oriented деле были в отдельно скомпилированном модуле, и 9X_language-theory хотя они были определены, как указано выше, при 9X_object-oriented-modeling компиляции X и W, позже они были изменены 9X_ood и перекомпилированы:

class Y : X { public override void Foo() { Console.WriteLine("YFoo"); } class Z : X { public override void Foo() { Console.WriteLine("ZFoo"); } 

Каким должен быть эффект 9X_oo-design от вызова W.Test()? Если бы программа должна была 9X_multiple-inheritance быть статически связана перед распространением, этап 9X_oop статической ссылки мог бы определить, что, хотя 9X_oop программа не имела двусмысленности до изменения 9X_object-orientation Y и Z, изменения Y и Z сделали вещи неоднозначными, и 9X_oops компоновщик мог отказаться строить программу 9X_object-orientation до тех пор, пока такая неоднозначность не 9X_oo-design будет разрешена. С другой стороны, возможно, что 9X_multiple-inheritance человек, у которого есть как W, так и новые 9X_object-oriented версии Y и Z, просто хочет запустить программу 9X_oop и не имеет исходного кода ни для одной из 9X_object-orientation них. Когда запускается W.Test(), больше не будет 9X_ood ясно, что должен делать W.Test(), но до тех пор, пока 9X_language-theory пользователь не попытается запустить W с 9X_ood новой версией Y и Z, никакая часть системы 9X_object-orientation не сможет распознать наличие ошибки. проблема 9X_oops (если W не считался незаконным даже до изменений 9X_object-oriented в Y и Z).

2
0