Як C ++ обробляє багаторазове успадкування спільним спільним предком?


13

Я не хлопець С ++, але я змушений думати про це. Чому можливе багатократне успадкування в C ++, а не в C #? (Я знаю проблему з алмазами , але це не про що я тут прошу). Як C ++ вирішує неоднозначність ідентичних підписів методів, успадкованих від декількох базових класів? І чому та сама конструкція не включена в C #?


3
Заради повноти, що таке "алмазна проблема", будь ласка?
jcolebrand

1
@jcolebrand en.wikipedia.org/wiki/Multiple_inheritance див. Diamond problem
l46kok

1
Звичайно, я погуглив це, але як я можу знати, що саме це означає Sandeep? Якщо ви збираєтесь посилатися на щось незрозуміле ім'я, коли "Багатократне успадкування із спільним спільним предком" є більш прямим ...
jcolebrand

1
@jcolebrand Я відредагував це, щоб відобразити те, що я отримав від питання. Я припускаю, що він має на увазі проблему з алмазами, на яку посилається у wikipedia з контексту. Наступного разу
відмовтеся

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

Відмінність від повного класу полягає в тому, що він не може містити жодних членів даних. Існує кілька варіантів здійснення цього. Очевидно, що множинне успадкування - це одне. Але багаторазове успадкування досить складно реалізувати. Але це насправді не потрібно. Натомість багато мов реалізують це шляхом розщеплення mixin в інтерфейсі, який реалізується класом та сховищем реалізацій методів, які або вводяться в сам клас, або генерується проміжний базовий клас, і вони розміщуються там. Це реалізовано в 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 буде введено багато успадкування методами розширення інтерфейсу.
Джорджіо

@Giorgio: Ні, багаторазове успадкування точно не буде запроваджено на Java. Mixins буде, що дуже різна річ, хоча вона охоплює багато інших причин використовувати множинне успадкування та більшість причин використовувати цікаво повторюваний шаблон шаблону (CRTP) і працює здебільшого як CRTP, а не як багатократне успадкування.
Ян Худек

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

@svick: Ні, це не так. Але альтернатива набагато менш ефективна, оскільки вимагає віртуальної розсилки для доступу членів.
Ян Худек

+1 для набагато повнішої відповіді, ніж моя.
Nathan C. Tresch

2

Відповідь полягає в тому, що він не працює належним чином у C ++ у випадку сутичок простору імен. Дивіться це . Щоб уникнути зіткнення простору імен, ви повинні робити всі види цирацій з покажчиками. Я працював у MS в команді Visual Studio, і я, принаймні частково, тому, що вони розробили делегацію, було взагалі уникнути зіткнення простору імен. Попередньо я говорив, що вони також розглядають інтерфейси як частину множинного рішення про спадкування, але я помилявся. Інтерфейси насправді дивовижні і їх можна змусити працювати в C ++, FWIW.

Делегація спеціально стосується зіткнення простору імен: Ви можете делегувати до 5 класів, і всі 5 з них експортуватимуть свої методи до вашої сфери дії як члени першого класу. Зовні, дивлячись на це IS багатократне успадкування.


1
Мені здається дуже малоймовірним, що це було основною причиною не включати ІМ в C #. Крім того, ця публікація не відповідає на питання, чому MI працює в C ++, коли у вас немає «сутичок простору імен».
Док Браун

@DocBrown Я працював у MS в команді Visual Studio, і запевняю, що це хоча б частково з тієї причини, що вони розробили делегування та інтерфейси, щоб взагалі уникнути зіткнення простору імен. Щодо якості питання, мех. Скористайтеся своєю головою, інші, здається, вважають, що це корисно.
Натан К. Треш

1
Я не маю наміру оскаржувати вашу відповідь, оскільки вважаю це правильним. Але ваша відповідь робить вигляд, що MI повністю непридатний для використання в C ++, і саме тому вони не ввели його в C #. Хоча мені не дуже подобається MI в C ++, я думаю, що це не зовсім невикористано.
Док Браун

1
Це одна з причин, і я не мав на увазі, що це ніколи не працює. Коли щось не є детермінованим у обчисленні, я схильний говорити, що він "зламаний", або що він "не працює", коли я маю на увазі "це як вуду, може, а може і не спрацювати, запалити свічки і молитися". : D
Натан К. Треш

1
є "зіткнення простору імен" недетермінованими?
Док Браун
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.