Чому можливе багатократне успадкування в 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 # реалізації є статичними методами повністю окремого класу, і синтаксис виклику методу відповідним чином інтерпретується компілятором, якщо методу заданого імені не існує, але визначено "метод розширення". Це має перевагу в тому, що методи розширення можуть бути додані до вже складеного класу, і недоліком є те, що такі методи не можна перекрити, наприклад, для забезпечення оптимізованої версії.