У чому полягає конкретна проблема багаторазового успадкування?


121

Я можу бачити людей, які весь час запитують, чи слід множинне спадкування включати до наступної версії C # або Java. Люди зі С ++, яким пощастило мати таку здатність, кажуть, що це як давати комусь мотузку, щоб врешті повіситися.

У чому справа з багаторазовим успадкуванням? Чи є конкретні зразки?


54
Я лише зазначу, що C ++ чудово підходить для того, щоб дати вам достатньо мотузки, щоб повіситись.
tloach

1
Для альтернативи багатократному успадкуванню, яке вирішує (і вирішує IMHO) багато тих самих проблем, подивіться на ознаки ( iam.unibe.ch/~scg/Research/Traits )
Беван,

52
Я думав, що С ++ дає тобі достатню кількість мотузки, щоб вистрілити собі в ногу.
KeithB

6
Це питання, мабуть, припускає, що існує проблема з ІМ загалом, тоді як я знайшов багато мов, де ІМ використовується у випадкових випадках. Звичайно, існують проблеми з поводженням з ІМ на певних мовах, але я не знаю, що ІМ взагалі має значні проблеми.
Девід Торнлі

Відповіді:


86

Найбільш очевидна проблема полягає в переборі функції.

Скажімо, є два класи Aі Bобидва визначають метод doSomething. Тепер ви визначаєте третій клас C, який успадковується від обох Aі B, але ви не перекриваєте doSomethingметод.

Коли компілятор запустить цей код ...

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

... яку реалізацію методу він повинен використовувати? Без додаткового уточнення компілятор неможливо вирішити двозначність.

Окрім важливої ​​проблеми, інша велика проблема багаторазового успадкування - це розташування фізичних об'єктів у пам'яті.

Такі мови, як C ++ та Java та C #, створюють фіксований адресний макет для кожного типу об’єктів. Щось на зразок цього:

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

Коли компілятор генерує машинний код (або байт-код), він використовує ці числові зсуви для доступу до кожного методу чи поля.

Багаторазове успадкування робить це дуже хитро.

Якщо клас Cуспадковує і те, Aі ін. B, Компілятор повинен вирішити, розміщувати дані в ABпорядку чи BAпорядку.

Але тепер уявіть, що ви викликаєте методи на Bоб’єкт. Це справді просто B? Або насправді Cоб'єкт називається поліморфно через його Bінтерфейс? Залежно від фактичної ідентичності об'єкта, фізичне розташування буде різним, і неможливо дізнатися про зміщення функції для виклику на сайті виклику.

Спосіб поводження з такою системою полягає у вириванні підходу з фіксованим компонуванням, що дозволяє кожному об'єкту запитувати його макет перед тим, як спробувати викликати функції або отримати доступ до його полів.

Отже ... короткий короткий сюжет ... це біль у шиї для авторів-компіляторів, які підтримують багатократне успадкування. Тож, коли хтось, як Гідо ван Россум, розробляє пітон, або коли Андерс Хейльсберг розробляє c #, вони знають, що підтримка багаторазового успадкування зробить реалізацію компілятора значно складнішою, і, мабуть, вони не вважають, що користь вартує витрат.


62
Ем, Python підтримує MI
Неманья Трифунович

26
Це не дуже переконливі аргументи - річ із фіксованим компонуванням зовсім не складна у більшості мов; в C ++ це складно, оскільки пам'ять непрозора, і, отже, у вас можуть виникнути труднощі з арифметичними припущеннями щодо покажчика. У мовах, де визначення класів є статичними (як у java, C # і C ++), кілька сутичок імен успадкування можуть бути заборонені час компіляції (а C # робить це так чи інакше з інтерфейсами!).
Еймон Нербонна

10
ОП просто хотіла розібратися в проблемах, і я пояснив їх, не особисто редагуючи це питання. Я щойно сказав, що мовні дизайнери та виконавці компіляторів "імовірно не вважають, що користь вартує витрат".
Бенджисміт

12
" Найбільш очевидна проблема з переосмисленням функції. " Це не має нічого спільного з переосмисленням функції. Це проста проблема неоднозначності.
curiousguy

10
Ця відповідь містить деяку неправильну інформацію про Guido та Python, оскільки Python підтримує MI. "Я вирішив, що поки я буду підтримувати спадщину, я можу також підтримувати простодушну версію множинної спадщини". - Guido van Rossum python-history.blogspot.com/2009/02/… - Плюс дозволу двозначності досить часто зустрічається у компіляторах (змінні можуть бути локальними для блокування, локальними для функціонування, локальними для функції, що вкладається, членами об'єктів, членами класу, глобалістів тощо), я не бачу, як додаткова сфера змінила б значення.
Маркус

46

Проблеми, про які ви згадуєте, хлопці, насправді не так важко вирішити. Насправді, наприклад, Ейфель робить це абсолютно добре! (і не вводячи довільного вибору чи іншого)

Наприклад, якщо ви успадковуєте від A і B, обидва мають метод foo (), то, звичайно, ви не хочете, щоб довільний вибір у вашому класі C успадкував і від A і B. Ви повинні або переосмислити foo, щоб було зрозуміло, що буде використовується, якщо викликається c.foo () або потрібно іншим способом перейменувати один із методів у C. (він може стати бар ())

Також я думаю, що багаторазове успадкування часто буває досить корисним. Якщо ви подивитеся на бібліотеки Ейфеля, то побачите, що він використовується повсюдно, і особисто я пропустив цю функцію, коли мені довелося повернутися до програмування на Java.


26
Я згоден. Основна причина, через яку люди ненавидять MI - це те саме, що і в JavaScript або зі статичним набором тексту: більшість людей коли-небудь використовували дуже погані його реалізації - або дуже погано використовували. Судити про MI по C ++ - це як судити про OOP по PHP або судити про автомобілі від Pintos.
Йорг W Міттаг

2
@curiousguy: MI вводить ще один набір ускладнень, які варто хвилювати, як і багато інших "особливостей" C ++. Тільки тому, що це однозначно, не полегшує роботу з налагодженням. Видалення цього ланцюжка, оскільки він вийшов з теми, і ви його все одно зняли.
Гуванте

4
@Guvante Єдина проблема з ІМ на будь-якій мові - це лайно програмісти, які думають, що вони можуть прочитати підручник і раптом знати мову.
Miles Rout

2
Я б заперечував, що мовні особливості стосуються не лише скорочення часу кодування. Вони також стосуються підвищення виразності мови та підвищення продуктивності.
Miles Rout

4
Крім того, помилки виникають від ІМ лише тоді, коли ідіоти використовують його неправильно.
Майлз Рут

27

Проблема з алмазами :

неоднозначність, яка виникає, коли два класи B і C успадковують від A, а клас D успадковує і B, і C. Якщо є метод A, що B і C мають перекрили , і D не переосмислює його, то яка версія метод успадковує D: B, чи C?

... Його називають "алмазною проблемою" через форму діаграми успадкування класу в цій ситуації. У цьому випадку клас A знаходиться у верхній частині, і B, і окремо під ним, і D з'єднується двома разом у нижній частині, утворюючи форму ромба ...


4
який має рішення, відоме як віртуальне успадкування. Проблема лише в тому випадку, якщо ти робиш це неправильно.
Ян Голдбі

1
@IanGoldby: Віртуальне успадкування - це механізм вирішення частини проблеми, якщо не потрібно дозволити збереження ідентифікацій оновлень та знижень серед усіх типів, з яких походить екземпляр, або для якого він може бути замінений . Дано X: B; Y: B; і Z: X, Y; припустимо, що someZ є екземпляром Z. При віртуальному успадкуванні (B) (X) someZ і (B) (Y) someZ є різними об'єктами; якщо це дано, можна отримати інше за допомогою пониження та оновлення, але що робити, якщо хтось має someZі хоче його подати Objectі потім B? Що Bце отримає?
supercat

2
@supercat Можливо, але подібні проблеми є в основному теоретичними, і в будь-якому випадку компілятор може сигналізувати. Важливим є усвідомлення того, яку проблему ви намагаєтеся вирішити, а потім використовувати найкращий інструмент, ігноруючи догму від людей, які швидше не стосуються себе розуміння "чому?"
Ян Голдбі

@IanGoldby: Про подібні проблеми компілятор може сигналізувати лише тоді, коли він має одночасний доступ до всіх розглянутих класів. У деяких структурах будь-яка зміна базового класу завжди потребуватиме перекомпіляції всіх похідних класів, однак можливість використовувати новіші версії базових класів без необхідності перекомпілювати похідні класи (для яких може не бути вихідного коду) є корисною функцією для каркасів, які можуть її надати. Крім того, проблеми не просто теоретичні. Багато класів у .NET розраховують на те, що передача від будь-якого посилального типу до Objectта назад до цього типу ...
supercat

3
@IanGoldby: Досить справедливо. Моя думка полягала в тому, що реалізатори Java та .NET не просто «лінувалися» у вирішенні не підтримувати узагальнений ІМ; підтримка узагальненого ІМ заважала б їх рамкам підтримувати різні аксіоми, чинність більшості користувачів корисніша, ніж МІ.
supercat

21

Багатократне успадкування - одна з тих речей, яка використовується не часто, і її можна неправильно використовувати, але іноді потрібна.

Я ніколи не розумів, що не додавати функцію, просто тому, що вона може бути неправильно використана, коли немає хороших альтернатив. Інтерфейси не є альтернативою багатократному успадкуванню. Для одного вони не дозволяють виконувати передумови чи постумови. Як і будь-який інший інструмент, ви повинні знати, коли його доречно використовувати, і як ним користуватися.


Чи можете ви пояснити, чому вони не дозволяють виконувати умови до і після?
Yttrill

2
@Yttrill, оскільки інтерфейси не можуть мати реалізацію методу. Куди ти кладеш assert?
curiousguy

1
@curiousguy: ви використовуєте мову з відповідним синтаксисом, який дозволяє вводити перед- і після умови безпосередньо в інтерфейс: не потрібно «стверджувати». Приклад від Felix: fun div (num: int, den: int when den! = 0): int очікує результат == 0 передбачає num == 0;
Іттріл

@Yttrill Гаразд, але деякі мови, як-от Java, не підтримують ані MI, ані «до- та після умови безпосередньо в інтерфейс».
curiousguy

Він не використовується часто, оскільки він недоступний, і ми не знаємо, як правильно його використовувати. Якщо ви подивитесь на якийсь код Scala, ви побачите, як все починає бути загальним і чи можна відновити риси ознак (Гаразд, це не ІМ, але доводить мою думку).
santiagobasulto

16

скажімо, у вас є об'єкти A і B, які успадковуються C. A і B обидва реалізують foo (), а C - ні. Я називаю C.foo (). Яка реалізація буде обрана? Є й інші питання, але цей тип речей - великий.


1
Але це насправді не конкретний приклад. Якщо і A, і B мають функцію, велика ймовірність, що і C потребуватиме власної реалізації. Інакше він все ще може викликати A :: foo () у власній функції foo ().
Пітер Кюне

@Quantum: Що робити, якщо цього не відбувається? Легко помітити проблему з одним рівнем успадкування, але якщо у вас багато рівнів і у вас є якась випадкова функція, яка десь удвічі, це стає дуже важкою проблемою.
tloach

Крім того, справа не в тому, що ви не можете викликати метод A або B, вказавши, який саме ви хочете, справа в тому, що якщо ви не вказуєте, тоді немає хорошого способу вибрати його. Я не впевнений, як C ++ справляється з цим, але якщо хтось знає, чи міг би це згадати?
tloach

2
@tloach - якщо C не вирішує двозначність, компілятор може виявити цю помилку і повернути помилку часу компіляції.
Еймон Нербонна

@Earmon - Через поліморфізм, якщо foo () є віртуальним, компілятор може навіть не знати під час компіляції, що це буде проблемою.
tloach

5

Основна проблема багаторазового успадкування добре підсумована на прикладі tloach. При успадкуванні від декількох базових класів, які реалізують ту саму функцію чи поле, компілятору належить прийняти рішення про те, яку реалізацію слід успадкувати.

Це стає гірше, коли ви успадковуєте з декількох класів, які успадковують від одного базового класу. (успадкування алмазу, якщо ви намалюєте дерево спадщини, ви отримаєте форму алмазу)

Ці проблеми насправді не є проблематичним для подолання компілятора. Але вибір, який тут повинен зробити компілятор, є досить довільним, це робить код набагато менш інтуїтивним.

Я вважаю, що, роблячи хороший дизайн OO, мені ніколи не потрібно багаторазове успадкування. У тих випадках, коли мені це потрібно, я зазвичай вважаю, що я використовую спадщину для повторного використання функціоналу, тоді як успадкування підходить лише для відносин "є-а".

Існують і інші методи, такі як міксин, які вирішують ті самі проблеми і не мають проблем із багаторазовим успадкуванням.


4
Скомпільований не повинен робити довільний вибір - він може просто помилитися. У C #, що таке тип ([..bool..]? "test": 1)?
Еймон Нербонна

4
У C ++ компілятор ніколи не робить таких довільних виборів: помилка визначення класу, де компілятору потрібно було б зробити довільний вибір.
curiousguy

5

Я не думаю, що проблема з алмазами є проблемою, я вважав би цю софістику, нічого іншого.

Найгірша проблема, з моєї точки зору, з багаторазовим успадкуванням - це жертви RAD і люди, які заявляють, що є розробниками, але насправді застрягли з половинними знаннями (у кращому випадку).

Особисто я був би дуже радий, якби нарешті я міг щось зробити в Windows Forms (це неправильний код, але він повинен дати вам думку):

public sealed class CustomerEditView : Form, MVCView<Customer>

Це головне питання, у якого я не маю багаторазового успадкування. Ви МОЖЕТЕ зробити щось подібне з інтерфейсами, але є те, що я називаю "s *** code", це болісний повторюваний c ***, який ви повинні написати в кожному з своїх класів, щоб отримати, наприклад, контекст даних.

На мою думку, не повинно бути абсолютно ніякої необхідності, не найменшої, у повторенні коду в сучасній мові.


Я схильний погоджуватися, але лише схильний: для виявлення помилок потрібна певна надмірність будь-якої мови. Так чи інакше, ви повинні приєднатися до команди розробників Felix, оскільки це головна мета. Наприклад, всі декларації є взаємно рекурсивними, і ви можете бачити вперед і назад, тому вам не потрібно переадресовувати декларації (область застосування встановлена ​​на зразок C, як мітки goto).
Yttrill

Я з цим повністю згоден - я просто зіткнувся з подібною проблемою тут . Люди говорять про проблему з діамантами, цитують це релігійно, але, на мою думку, це так легко уникнути. (Нам не всім потрібно писати наші програми так, як вони написали бібліотеку iostream.) Множинне успадкування логічно повинно використовуватися, коли у вас є об'єкт, який потребує функціонування двох різних базових класів, які не мають функцій перекриття або імен функцій. У правих руках - це інструмент.
jedd.ahyoung

3
@Turing Complete: wrt не має повторення коду: це хороша ідея, але вона неправильна і неможлива. Існує величезна кількість моделей використання, і ми хочемо абстрагувати загальні в бібліотеці, але сміливо абстрагувати їх усі, тому що навіть якби ми могли смислове навантаження при запам'ятовуванні всіх імен занадто велике. Те, що ви хочете - приємний баланс. Не забувайте повторення - це те, що надає структурі речей (модель передбачає надмірність).
Іттріл

@ lunchmeat317: Те, що код взагалі не повинен писатися таким чином, щоб "алмаз" створював проблему, не означає, що дизайнер мов / рам може просто ігнорувати проблему. Якщо рамка передбачає, що оновлення та посилання вниз зберігають ідентичність об'єкта, бажає дозволити в пізніших версіях класу збільшити кількість типів, на які він може бути замінений, не змінюючи перешкоди, і бажає дозволити створення типу виконання, Я не думаю, що це може дозволити успадкування декількох класів (на відміну від успадкування інтерфейсу), виконуючи вищезазначені цілі.
supercat

3

Загальна система об'єктів Lisp (CLOS) - це ще один приклад того, що підтримує MI, уникаючи проблем у стилі C ++: успадковуванню надається розумний дефолт , при цьому все ж ви дозволяєте чітко вирішувати, як саме, скажімо, називати поведінку супер .


Так, CLOS - одна з найдосконаліших об'єктних систем з моменту створення сучасних обчислень, можливо, навіть давно минулого :)
rostamn739

2

Немає нічого поганого в самому багатократному успадкуванні. Проблема полягає в тому, щоб додати кілька успадковань до мови, яка не була створена з урахуванням багатократного успадкування з самого початку.

Ейфелева мова підтримує багатократне успадкування без обмежень дуже ефективним і продуктивним способом, але мова була розроблена з цього початку для її підтримки.

Цю особливість складно реалізувати для розробників-компіляторів, але, схоже, цей недолік можна було б компенсувати тим, що хороша підтримка множинного успадкування може уникнути підтримки інших функцій (тобто не потрібно інтерфейсу чи методу розширення).

Я думаю, що підтримка багаторазового успадкування чи ні - це більше питання вибору, питання пріоритетів. Більш складна функція потребує більше часу для правильної реалізації та експлуатації та може бути більш суперечливою. Реалізація C ++ може бути причиною того, що множинне успадкування не було реалізовано в C # та Java ...


1
Підтримка ІМ для C ++ не " дуже ефективна та продуктивна "?
curiousguy

1
Насправді він дещо зламаний, в тому сенсі, що він не вписується в інші функції C ++. Присвоєння не працює належним чином із спадкуванням, не кажучи вже про багаторазове успадкування (ознайомтесь із дійсно поганими правилами). Створювати алмази правильно, настільки важко Комітет стандартів накрутив ієрархію винятків, щоб зробити його простою та ефективною, а не робити це правильно. На старшому компіляторі, який я використовував у той час, коли я тестував це, і декілька MI-комбінацій та реалізація базових винятків коштували понад мегабайт коду, і на його складання пішло 10 хвилин. Просто визначення.
Іттріл

1
Алмази - хороший приклад. У Ейфелі алмаз вирішено чітко. Наприклад, уявіть собі, що Студент і Вчитель успадковують від Особи. У людини є календар, тому і Учень, і Вчитель успадкують цей календар. Якщо ви будуєте алмаз, створюючи TeachingStudent, який успадковується і від Вчителя, і від Студента, ви можете вирішити перейменувати один із успадкованих календарів, щоб обидва календарі були доступні окремо, або вирішили об'єднати їх так, щоб він поводився більше як Особа. Багаторазове успадкування може бути реалізовано непогано, але воно вимагає ретельного проектування, бажано з самого початку ...
Крістіан Лемер

1
Ейфелеві компілятори повинні зробити глобальний аналіз програми для ефективної реалізації цієї моделі ІМ. Для викликів поліморфних методів вони використовують або диспетчерські громи, або розріджені матриці, як пояснено тут . Це не добре поєднується з роздільною компіляцією C ++ та функцією завантаження класу C # і Java.
cyco130

2

Однією з цілей дизайну рамок, таких як Java та .NET, є можливість коду, який складається, працювати з однією версією попередньо складеної бібліотеки, однаково добре працювати з наступними версіями цієї бібліотеки, навіть якщо ці наступні версії додати нові функції. У той час як звичайна парадигма на таких мовах, як C або C ++, полягає в поширенні статично пов'язаних виконуваних файлів, що містять усі необхідні їм бібліотеки, парадигма в .NET і Java полягає в розповсюдженні програм у вигляді колекцій компонентів, які "пов'язані" під час виконання. .

Модель COM, що передувала .NET, намагалася використовувати цей загальний підхід, але насправді наслідування не мало - натомість кожне визначення класу ефективно визначало як клас, так і інтерфейс з тим же ім'ям, який містив усіх його публічних членів. Екземпляри були типу класу, тоді як посилання були типу інтерфейсу. Оголошено клас таким, що походить від іншого, було рівнозначним оголошенню класу як інтерфейсу іншого та вимагав від нового класу повторної реалізації всіх публічних членів класів, з яких похідний. Якщо Y і Z походять від X, а тоді W походить від Y і Z, не має значення, якщо Y і Z по-різному реалізують члени X, тому що Z не зможе використовувати їх реалізацію - доведеться визначити його власний. W може інкапсулювати екземпляри Y та / або Z,

Складність у Java та .NET полягає в тому, що коду дозволено успадковувати членів і мати доступ до них неявно посилається на батьківські члени. Припустимо, один з класів WZ, як описано вище:

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

Здавалося б, W.Test()слід створити екземпляр W call реалізації віртуального методу, Fooвизначеного в X. Припустимо, однак, що Y і Z насправді були окремо складеним модулем, і хоча вони були визначені як вище, коли компілюються X і W, вони пізніше були змінені і перекомпільовані:

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

Тепер яким повинен бути ефект дзвінка W.Test()? Якщо програму довелося статично зв’язати перед розповсюдженням, етап статичного зв’язку може виявити, що, хоча програма не мала двозначності перед зміною Y і Z, зміни Y і Z зробили речі неоднозначними, і лінкер міг відмовитись будувати програму, доки не буде вирішена така двозначність. З іншого боку, можливо, що людина, яка має і W, і нові версії Y і Z, - це хтось, хто просто хоче запустити програму і не має вихідного коду для жодної з неї. Коли W.Test()працює, то вже не буде зрозуміло, щоW.Test() слід робити, але поки користувач не спробував запустити W з новою версією Y і Z, жодна частина системи не змогла б визнати, що виникає проблема (якщо тільки W не вважалася нелегітимною ще до зміни Y і Z ).


2

Діамант не є проблемою, якщо ви не використовуєте нічого подібного до віртуальної спадщини C ++: у звичайному успадкуванні кожен базовий клас нагадує поле члена (насправді вони викладені в оперативній пам'яті таким чином), що дає вам синтаксичний цукор і додаткова можливість перекрити більше віртуальних методів. Це може ввести певну неоднозначність під час компіляції, але це зазвичай легко вирішити.

З іншого боку, з віртуальною спадщиною він занадто легко виходить з-під контролю (і тоді стає безладом). Розглянемо в якості прикладу діаграму «серця»:

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

У C ++ це абсолютно неможливо: як тільки Fі Gоб'єднаються в єдиний клас, їх As теж об'єднуються, проміжок часу. Це означає , що ви ніколи не розглядати базові класи непрозорих в C ++ (в цьому прикладі ви повинні побудувати Aв Hтак що ви повинні знати , що вона присутня де - то в ієрархії). Однак іншими мовами це може працювати; наприклад, Fі Gможе явно оголосити A як "внутрішній", забороняючи таким чином послідовні злиття і ефективно роблячи себе міцними.

Ще один цікавий приклад ( не специфічний для C ++):

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

Тут Bвикористовується лише віртуальне успадкування. Так Eміститься два Bs, які поділяють одне і те ж A. Таким чином, ви можете отримати A*покажчик , який вказує на E, але ви не можете кинути його до B*вказівником , хоча об'єкт є на насправді B як такої кидок неоднозначна, і ця невизначеність не може бути виявлена під час компіляції (якщо компілятор не бачить ціла програма). Ось код тесту:

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<A *>(e); // works, A is unambiguous
//      B *b = dynamic_cast<B *>(e); // doesn't compile
        B *b = dynamic_cast<B *>(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<B *>(dynamic_cast<C *>(e)) << std::endl;
        std::cout << "A::D::B: " << dynamic_cast<B *>(dynamic_cast<D *>(e)) << std::endl;
        std::cout << "A=>C=>B: " << dynamic_cast<B *>(dynamic_cast<C *>(a)) << std::endl;
        std::cout << "A=>D=>B: " << dynamic_cast<B *>(dynamic_cast<D *>(a)) << std::endl;
        return 0;
}

Більше того, реалізація може бути дуже складною (залежить від мови; див. Відповідь Бенджисміта).


Ось справжня проблема з ІМ. Програмістам можуть знадобитися різні резолюції в межах одного класу. Загальнонаціональне рішення обмежило б можливе і змусило програмістів створювати недоліки для правильної роботи програми.
shawnhcorey
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.