Страницы

Поиск по вопросам

Показаны сообщения с ярлыком множественное-наследование. Показать все сообщения
Показаны сообщения с ярлыком множественное-наследование. Показать все сообщения

четверг, 9 января 2020 г.

Вызов методов родителей при множественном наследовании в Python

#python #наследование #множественное_наследование


Копипаст из idle

class A(object):
    def go(self):
        print("go A go!")
    def stop(self):
        print("stop A stop!")
    def pause(self):
        raise Exception("Not Implemented")

class B(A):
    def go(self):
        super(B, self).stop()
        print("go B go!")


class C(A):
    def go(self):
        super(C, self).go()
        print("go C go!")
    def stop(self):
        super(C, self).stop()
        print("stop C stop!")


class E(B,C): pass

e=E()


e.go() вывел

stop A stop!
stop C stop!
go B go!


Непонятно откуда такой вывод
    


Ответы

Ответ 1



Сначала немного теории: Когда вы дёргаете метод у объекта, интерпретатор пытается найти этот метод сначала у самого объекта, если не находит - то ищет у класса объекта, если и там не находит, то по очереди обходит всех предков класса, пока не найдёт. Порядок обхода предков при множественном наследовании определяется алгоритмом MRO C3. Хорошая статья об этом есть на Хабре: https://habr.com/ru/post/62203/ Для вашего класса E в соответствии с MRO C3 порядок обхода родителей будет таким: E -> B -> C -> A -> object В этом легко убедиться так: print(E.__mro__) Теперь по пунктам, что происходит в вашем коде: Вы дёргаете для объекта e метод go Интерпретатор ищёт go в e. Не находит. Ищет go в классе E. Не находит. Ищет go в классе B. Находит, начинает исполнять. Ему нужно выполнить super(B, self).stop() Так как в данном случае self - это e (то есть объект класса E), то он будет делать обход в соответствии с порядком B -> C -> A. И, следовательно, будет искать stop не среди предков класса B, а в следующем по цепочке классе C. В C метод stop есть, но в нём делается сначала super(C, self).stop() поэтому интерпретатор пойдёт в A.stop В A.stop он напечатает "stop A stop!", после чего вернётся в C.stop В C.stop он напечатает "stop C stop!", после чего вернётся в B.go В B.go он напечатает "go B go!" Вот и всё :)

вторник, 24 декабря 2019 г.

Расширение интерфейса библиотеки

#cpp #visual_cpp #наследование #полиморфизм #множественное_наследование


В книге Брюса Эккеля "Философия С++ часть 2" автор приводит пример использование
множественного наследование в качестве средства для расширения абстрактного класса
библиотеки, к которой нет доступа. 
Суть задачи такова: есть интерфейс Vendor, редактировать код которого нельзя. Есть
класс Pastel, с которым должны использоваться функции из библиотеки, в которой определен
Vendor (принимают ссылку или указатель на Vendor), но функционала этого интерфейса
недостаточно. 
Автор приводит решение:

class MyBase{
public:
    virtual void v()const=0;
    virtual void g()const=0;
}
class Pastel: public MyBase, public Vendor{
/*...*/
}


Вопрос: в чем преимущество такого подхода к расширению над таким:

class MyBase: public Vendor{
public:
    virtual void v()const=0;
    virtual void g()const=0;
}
class Pastel: public MyBase{
/*...*/
}

    


Ответы

Ответ 1



Представьте, что MyDeviceInterface - это интерфейсный класс, который определяет некоторые методы для интеграции классов в проект. class MyDeviceInterface { virtual int device_id() const = 0; virtual char device_name() const = 0; } А где-то в проекте есть общий метод опроса девайсов, типа ... std::vector my_devices = GetAvailableDevices(); for (MyDeviceInterface* device : my_devices) { std::cout << device->device_id() << std::endl; std::cout << device->device_name() << std::endl; } ... Теперь представьте, что у вас есть 5 классов девайсов, которые нельзя редактировать: Device1...Device5, причем каждый из этих классов имеет свою неповторимую реализацию методов :) В первом случае вам необходимо будет написать 5 классов для своего проекта, в которых вы просто опишите реализацию работы интерфейса class MyDevice1 : public MyDeviceInterface, public Device1 { int device_id() const { return Device1::get_device1_id(); } char device_name() const { return Device1::get_device1_name(); } } ... class MyDevice5 : public MyDeviceInterface, public Device5 { int device_id() const { return Device5::get_device5_id(); } char device_name() const { return Device5::get_device5_name(); } } Получается чистый и красивый код, интерфейсный класс MyDeviceInterface собственно определяет интерфейс, а реализацию уже можно брать из классов Device1..5. Также наличие класса интерфейса позволяет манипулировать всеми девайсами как некими абстрактными сущностями, которые подчиняются общим правилам поведения, а жесткое разделение наследования защищает наши классы друг от друга (класс MyDevice1 ничего не знает о детялях реализации классов MyDevice2 и т.п.) Также довольно просто добавлять новые девайсы в такой код, мы просто штампуем наследные классы от MyDeviceInterface и DeviceN. Во втором случае у вас такое тоже получится, но придется создать какой-то супер-интерфейс: class MyDeviceInterface : public Device1, ... , public Device5 { virtual int device_id() const = 0; virtual char device_name() const = 0; } и классы реализации class MyDevice1 : public MyDeviceInterface { int device_id() const { return Device1::get_device1_id(); } char device_name() const { return Device1::get_device1_name(); } } ... class MyDevice5 : public MyDeviceInterface { int device_id() const { return Device5::get_device5_id(); } char device_name() const { return Device5::get_device5_name(); } } Но, в этом случае все девайсы знают друг про друга, что небезопасно, методы классов Device1..5 могут иметь коллизии имен и тому подобное. Добавление каждого нового девайса будет раздувать MyDeviceInterface все больше и больше, и, в конечном итоге, это все станет невозможно сопровождать и проект умрет. Можно создать просто 5 классов девайсов class MyDevice1 : public Device1 { int device_id() const { return Device1::get_device1_id(); } char device_name() const { return Device1::get_device1_name(); } } Но тогда вы теряете абстрактность, т.к. у классов нету общего родителя. Эту потерю можно компенсировать шаблонами, но опять же при добавлении каждого нового девайса код будет неизбежно раздуваться за счет разворачивания шаблонов, что может быть критично для устройств с ограниченным объемом памяти. Кроме того шаблоны трудны в отладке. Однако такой способ тоже может иметь место на жизнь.

пятница, 13 декабря 2019 г.

Множественное наследование и VC++

#cpp #visual_cpp #множественное_наследование


В ходе дискуссии пришли к такой программе:

#include 
using namespace std;

class A
{
protected:
    int var;
public:
    A(int x)
    {
        var = x;    // Это обращение к A::var
    }
};

class B: public A
{
protected:
    int var;
public:
    B():A(2)
    {
        var = 4;  // Обращение к B::var
    }
};

class C: public A
{
protected:
    int var;
public:
    C():A(3)
    {
        var = 6;    // Обращение к C::var
    }
};


class D: public B, public C
{
protected:
    int var;
public:

    void method()
    {
        var = B::A::var;       // Должен выдать 2
        cout << var << endl;

        var = C::A::var;       // Должен выдать 3
        cout << var << endl;

        var = B::var;          // Должен выдать 4
        cout << var << endl;

        var = C::var;          // Должен выдать 6
        cout << var << endl;
    }
};

int main()
{
    D obj;
    obj.method();
}


Программа отлично компилируется и выводит то, что и ожидалось - в Visual C++ 2015.
Попытка скомпилировать с помощью GCC на ideone.com дает массу ошибок:

prog.cpp: In member function 'void D::method()':
prog.cpp:46:21: error: 'A' is an ambiguous base of 'D'
         var = B::A::var;       // Должен выдать 2
                     ^
prog.cpp:49:21: error: 'A' is an ambiguous base of 'D'
         var = C::A::var;       // Должен выдать 3
                     ^


Вопрос к знатокам стандарта - кто тут неправ, а кто прав? Если неправ VC++, то в
чем, почему и как надо поступать правильно?

Update 22.10.2016

Пожалуй, наиболее переносимо будет не полагаться на name lookup, а воспользоваться
преобразованиями в духе 

var = static_cast(static_cast(this))->var;
var = ((A*)(C*)this)->var;


Но при этом нужно делать var в A public'ом. (Опять же не понимаю, почему и где на
это ссылка в стандарте...) Полный код тут - http://ideone.com/wt9yrz
    


Ответы

Ответ 1



Как правильно указал @Ant, студия здесь неправа и всё дело в том, что подобные обращения A::B::C::D::E::F являются именно обращениями к вложенным(nested) сущностям, т.е. B должен быть сущностью вложенной в A, С в B и так далее. Это можно видеть в не нормативной ссылке в стандарте: [basic.lookup.qual]p2 [Note: Multiply qualified names, such as N1::N2::N3::n, can be used to refer to members of nested classes (9.7) or members of nested namespaces. — end note] Т.е. мы можем ссылаться на несколько уровней, но эти ссылки должны идти по нисходящей, никаких восходящих, либо же смешанных уровней. Тогда почему это вообще компилируется?(оно компилируется пока не встречает неоднозначность, уберём неоднозначность и всё заработает). Потому что есть следующее правило по поиску скрытых имён: [class.qual]p3 A class member name hidden by a name in a nested declarative region or by the name of a derived class member can still be found if qualified by the name of its class followed by the :: operator. И тут получается, что C::A::var должно интерпретироваться как A::var, где C:: является избыточным квалификатором, который не несёт никакой смысловой нагрузки. Никакой другой интерпретации тут быть не может, т.к. C не содержит внутреннего класса с именем A. Суть ошибки, кстати, хорошо видна с Resharper: он сразу показывается, что квалификаторы C:: и B:: избыточны. Что касается второй части вопроса(обновления): не работает это по простой причине: классы наследники имеют доступ к данным предка только через this, у них нет доступа к данным произвольных объектов этих классов. Это описано в [class.access.base]p5, а конкретно, случай из вопроса, в (5.3): m as a member of N is protected, and R occurs in a member or friend of class N, or in a member or friend of a class P derived from N, where m as a member of P is public, private, or protected,

Ответ 2



Прав GCC. Имена вида B::A::var и C::A::var - это не более чем квалифицированные имена, включающие в качестве nested-name-specifier имена класс-типов B::A и C::A. Это не более чем однозначный способ сослаться на сам базовый тип, в котором будет искаться имя var. И имя B::A, и имя C::A ссылаются на один и тот же базовый тип A. Он же - ::A. По этой причине оба имени эквивалентны друг другу и эквивалентны также ::A::var. То есть для расширения эксперимента вы можете добавить в функцию method() еще и доступ через ::A::var и получить абсолютно ту же саму ошибку. Для того, чтобы подчеркнуть эту эквивалентность, можно переписать код внутри method() так typedef B::A BA; typedef C::A CA; typedef ::A AA; static_assert(std::is_same::value); static_assert(std::is_same::value); BA::var; // неоднозначность CA::var; // неоднозначность AA::var; // неоднозначность Очевидно, что все три имени - BA, CA и AA - обозначают один и тот же тип. Поэтому нет никаких причин ожидать, что доступы через BA::var, CA::var или AA::var будут вести себя по-разному. Обратите также внимание, что если вы устраните множественное наследование (и вызванную им неоднозначность), то доступ через ::A::var будет прекрасно работать внутри method(), несмотря на то, что если рассматривать его как способ задания "пути доступа", то он выглядит "неправильно". Другими словами, nested-name-specifier в qualified-id не рассматривается языком как указание "пути прохождения" через вложенные scopes в процессе name lookup. Язык в данном случае рассматривает nested-name-specifier лишь как способ задания scope, в котором следует искать имя, после чего это имя интерпретируется "на общих основаниях" в том контексте, в котором оно использовано. P.S. Интересно заметить, что в исходном примере MSVC допускает такое обращение void method() { var = ::A::var; cout << var << endl; } и выводит на печать 2. То есть несмотря на то, что никаких мер по устранению неоднозначности мы не предприняли, MSVC смело полагает, что "победить" в этом случае должна A::bar из B (очевидно, первой по списку базы D). Таких правил в спецификации языка нет.

воскресенье, 1 декабря 2019 г.

Почему в C# отказались от множественного наследования классов?

#c_sharp #множественное_наследование


Друзья, объясните или скиньте ссылки, где можно найти ответ на вопрос "Почему в 
C# отказались от множественного наследования классов ?"
    


Ответы

Ответ 1



TL;DR: добавление множественно наследования добавляет много сложностей, и мало выгоды от использования. перевод ответа Chris Brumme на вопрос Why doesn’t C# support multiple inheritance? Есть несколько причин почему мы не реализовали множественное наследование реализации напрямую. (Как вы знаете, мы поддерживаем множественное наследование интерфейсов). Однако, я должен обратить внимание на то, что компиляторы могут создавать множественное наследование для их типов внутри CLR. Есть несколько проблем, если идти этим путем: результат непроверяем, отсутствие взаимодействия с другими языками через CLS. Техника для создания некоторых VTables в RVA-шных статических полях. Для того, чтобы адреса управляемых методов (которые, возможно, даже не были обработаны JIT), вы используете конструкцию VTFixup. Эта конструкция представляет собой таблицу триплетов. Триплеты состоят из маркера на управляемый метод, адреса в вашем образе, который должен будет зафиксироваться (в этом случае, слот в VTable вы создаете в RVA-шных статических полях), и некоторых флагов. Возможные флаги описаны в corhdr.h. Они позволяют указать размер указателей: 32 бита vs 64 бита, контролировать виртуальное поведение, а также есть ли какой-нибудь режим обратного PInvoke, который следует применять в виде отложенных вычислений, что в итоге передаются в управляемый метод. Если мы выполняем переход unmanaged->managed, мы так же можем контролировать какой AppDomain будет выбран, чтобы отправить вызов. Есть несколько причин, почему мы не предоставили продуманную, поддающуюся проверке, CLS-совместимую версию множественного наследования реализации: Разные языки имеют разные ожидания того, как работает множественное наследование. Например, как решаются конфликты и будут ли дубликаты объединены или отброшены. Прежде чем мы сможем реализовать множественное наследование в CLR, мы должны сделать обзор всех языков, определить общие понятия, и решить как выразить их нейтральным способом. Мы должны решить будет ли множественное наследование входить в CLS и что это будет означать для языков, которые не хотят поддерживать эту концепцию (например VB.NET). Конечно это наше дело в CLR, но мы пока не нашли время сделать это для множественного наследования. Мест, где хорошо подходит множественное наследование, на самом деле очень мало. Во многих случаях работает множественное наследование интерфейсов. В других случаях можно использовать инкапсуляцию и делегирование. Если бы мы добавили другую конструкцию, например mixins, что было бы более мощным? Множественное наследование реализации добавляет сложности для реализации. Эта сложность влияет на кастинг, сериализацию, доступ к полям рефлексию и возможно многие другие места. Все еще не совсем ясно, что эта возможность окупит себя. Это то о чем мы часто спрашиваем себя. Это нечто, что мы еще не до конца проверили. Но мое нутро говорит, что после глубокого анализа мы решим не добавлять эту особенность.

вторник, 25 июня 2019 г.

Имитация множественного наследования

Есть класс и функция-конструктор. Делается попытка реализовать класс, являющийся чем-то типа потомка обоих. Точнее, методы из прототипа функции-конструктора копируются в прототип класса-потомка, унаследованный от класса.
Да, я понимаю, что это не полноценное наследование, но для решения задачи этого хватает.
Проблема в другом. Как заставить тайпскрипт воспринимать копируемые методы?
class First { someMethod() { console.log('someMethod from First'); } }
function Second() { console.log('Second'); }
Second.prototype.doSmth = function () { console.log('doSmth from Second'); }
interface IBoth { someMethod() doSmth() }
class Both extends First /* implements IBoth */ { constructor() { console.log('constructor of Both'); super(); Second.call(this); } }
for (let key in Second.prototype) { Both.prototype[key] = Second.prototype[key]; }
На самом деле, надо обеспечить видимость методов ещё на уровень дальше
class Final extends Both { doIt() { this.someMethod(); //this.doSmth(); // Надо заставить видеть метод тут (this as any as IBoth).doSmth(); // Компилируется, но это ужас } }
если при этом методы не будут видны из самого Both, то это годится.
Вот что я уже пробовал:
При попытке написать
class Both extends First implements IBoth {
возникает ошибка, что я не реализую методы интерфейса. При переименовании Both в _Both и использовании
var Both = _Both as typeof _Both;
всё остаётся как было, что логично, поскольку тут никак не используется First При переименовании Both в _Both и использовании
var Both = _Both as typeof IBoth;
говорит, что не может найти имя IBoth
Пробовал ещё несколько вариантов, но они совсем бредовые. Что ещё можно сделать?

Попробовать можно тут: http://www.typescriptlang.org/Playground Полный код для проверки
Запустить (код из правой панели) после добавления строки:
(new Final).doIt();
Вывод при запуске при раскомментированной строке this.doSmth();
constructor of Both Second someMethod from First doSmth from Second doSmth from Second

PS: Этот вопрос на английском.


Ответ

Интерфейс вообще не нужен, нужно просто объявить прототипное поле с нужным типом:
doSmth: () => void
Она видима как свойство, а не как метод, но это непринципиально.
Полный код:
class First { someMethod() { console.log('someMethod from First'); } }
function Second() { console.log('Second'); }
Second.prototype.doSmth = function () { console.log('doSmth from Second'); }
class Both extends First { constructor() { console.log('constructor of Both'); super(); Second.call(this); }
doSmth: () => void }
for (let key in Second.prototype) { Both.prototype[key] = Second.prototype[key]; }
class Final extends Both { doIt() { this.someMethod(); this.doSmth(); //Both.prototype.doSmth(); // ok //Final.prototype.doSmth(); // ok } }
PS: Надо было гуглить не всяческие варианты с наследованием, а typescript class prototype variable - сразу нашёл подходящий вариант.

понедельник, 26 ноября 2018 г.

Расширение интерфейса библиотеки

В книге Брюса Эккеля "Философия С++ часть 2" автор приводит пример использование множественного наследование в качестве средства для расширения абстрактного класса библиотеки, к которой нет доступа. Суть задачи такова: есть интерфейс Vendor, редактировать код которого нельзя. Есть класс Pastel, с которым должны использоваться функции из библиотеки, в которой определен Vendor (принимают ссылку или указатель на Vendor), но функционала этого интерфейса недостаточно. Автор приводит решение:
class MyBase{ public: virtual void v()const=0; virtual void g()const=0; } class Pastel: public MyBase, public Vendor{ /*...*/ }
Вопрос: в чем преимущество такого подхода к расширению над таким:
class MyBase: public Vendor{ public: virtual void v()const=0; virtual void g()const=0; } class Pastel: public MyBase{ /*...*/ }


Ответ

Представьте, что MyDeviceInterface - это интерфейсный класс, который определяет некоторые методы для интеграции классов в проект.
class MyDeviceInterface { virtual int device_id() const = 0; virtual char device_name() const = 0; }
А где-то в проекте есть общий метод опроса девайсов, типа
... std::vector my_devices = GetAvailableDevices();
for (MyDeviceInterface* device : my_devices) { std::cout << device->device_id() << std::endl; std::cout << device->device_name() << std::endl; } ...
Теперь представьте, что у вас есть 5 классов девайсов, которые нельзя редактировать: Device1...Device5, причем каждый из этих классов имеет свою неповторимую реализацию методов :)
В первом случае вам необходимо будет написать 5 классов для своего проекта, в которых вы просто опишите реализацию работы интерфейса
class MyDevice1 : public MyDeviceInterface, public Device1 { int device_id() const { return Device1::get_device1_id(); } char device_name() const { return Device1::get_device1_name(); } }
...
class MyDevice5 : public MyDeviceInterface, public Device5 { int device_id() const { return Device5::get_device5_id(); } char device_name() const { return Device5::get_device5_name(); } }
Получается чистый и красивый код, интерфейсный класс MyDeviceInterface собственно определяет интерфейс, а реализацию уже можно брать из классов Device1..5. Также наличие класса интерфейса позволяет манипулировать всеми девайсами как некими абстрактными сущностями, которые подчиняются общим правилам поведения, а жесткое разделение наследования защищает наши классы друг от друга (класс MyDevice1 ничего не знает о детялях реализации классов MyDevice2 и т.п.) Также довольно просто добавлять новые девайсы в такой код, мы просто штампуем наследные классы от MyDeviceInterface и DeviceN.
Во втором случае у вас такое тоже получится, но придется создать какой-то супер-интерфейс:
class MyDeviceInterface : public Device1, ... , public Device5 { virtual int device_id() const = 0; virtual char device_name() const = 0; }
и классы реализации
class MyDevice1 : public MyDeviceInterface { int device_id() const { return Device1::get_device1_id(); } char device_name() const { return Device1::get_device1_name(); } }
...
class MyDevice5 : public MyDeviceInterface { int device_id() const { return Device5::get_device5_id(); } char device_name() const { return Device5::get_device5_name(); } }
Но, в этом случае все девайсы знают друг про друга, что небезопасно, методы классов Device1..5 могут иметь коллизии имен и тому подобное. Добавление каждого нового девайса будет раздувать MyDeviceInterface все больше и больше, и, в конечном итоге, это все станет невозможно сопровождать и проект умрет.
Можно создать просто 5 классов девайсов
class MyDevice1 : public Device1 { int device_id() const { return Device1::get_device1_id(); } char device_name() const { return Device1::get_device1_name(); } }
Но тогда вы теряете абстрактность, т.к. у классов нету общего родителя. Эту потерю можно компенсировать шаблонами, но опять же при добавлении каждого нового девайса код будет неизбежно раздуваться за счет разворачивания шаблонов, что может быть критично для устройств с ограниченным объемом памяти. Кроме того шаблоны трудны в отладке. Однако такой способ тоже может иметь место на жизнь.

пятница, 19 октября 2018 г.

Множественное наследование и VC++

В ходе дискуссии пришли к такой программе:
#include using namespace std;
class A { protected: int var; public: A(int x) { var = x; // Это обращение к A::var } };
class B: public A { protected: int var; public: B():A(2) { var = 4; // Обращение к B::var } };
class C: public A { protected: int var; public: C():A(3) { var = 6; // Обращение к C::var } };
class D: public B, public C { protected: int var; public:
void method() { var = B::A::var; // Должен выдать 2 cout << var << endl;
var = C::A::var; // Должен выдать 3 cout << var << endl;
var = B::var; // Должен выдать 4 cout << var << endl;
var = C::var; // Должен выдать 6 cout << var << endl; } };
int main() { D obj; obj.method(); }
Программа отлично компилируется и выводит то, что и ожидалось - в Visual C++ 2015. Попытка скомпилировать с помощью GCC на ideone.com дает массу ошибок:
prog.cpp: In member function 'void D::method()': prog.cpp:46:21: error: 'A' is an ambiguous base of 'D' var = B::A::var; // Должен выдать 2 ^ prog.cpp:49:21: error: 'A' is an ambiguous base of 'D' var = C::A::var; // Должен выдать 3 ^
Вопрос к знатокам стандарта - кто тут неправ, а кто прав? Если неправ VC++, то в чем, почему и как надо поступать правильно?
Update 22.10.2016
Пожалуй, наиболее переносимо будет не полагаться на name lookup, а воспользоваться преобразованиями в духе
var = static_cast(static_cast(this))->var; var = ((A*)(C*)this)->var;
Но при этом нужно делать var в A public'ом. (Опять же не понимаю, почему и где на это ссылка в стандарте...) Полный код тут - http://ideone.com/wt9yrz


Ответ

Как правильно указал @Ant, студия здесь неправа и всё дело в том, что подобные обращения A::B::C::D::E::F являются именно обращениями к вложенным(nested) сущностям, т.е. B должен быть сущностью вложенной в A, С в B и так далее. Это можно видеть в не нормативной ссылке в стандарте:
[basic.lookup.qual]p2 [Note: Multiply qualified names, such as N1::N2::N3::n, can be used to refer to members of nested classes (9.7) or members of nested namespaces. — end note]
Т.е. мы можем ссылаться на несколько уровней, но эти ссылки должны идти по нисходящей, никаких восходящих, либо же смешанных уровней.
Тогда почему это вообще компилируется?(оно компилируется пока не встречает неоднозначность, уберём неоднозначность и всё заработает). Потому что есть следующее правило по поиску скрытых имён:
[class.qual]p3 A class member name hidden by a name in a nested declarative region or by the name of a derived class member can still be found if qualified by the name of its class followed by the :: operator.
И тут получается, что C::A::var должно интерпретироваться как A::var, где C:: является избыточным квалификатором, который не несёт никакой смысловой нагрузки. Никакой другой интерпретации тут быть не может, т.к. C не содержит внутреннего класса с именем A.
Суть ошибки, кстати, хорошо видна с Resharper: он сразу показывается, что квалификаторы C:: и B:: избыточны.

Что касается второй части вопроса(обновления): не работает это по простой причине: классы наследники имеют доступ к данным предка только через this, у них нет доступа к данным произвольных объектов этих классов. Это описано в [class.access.base]p5, а конкретно, случай из вопроса, в (5.3):
m as a member of N is protected, and R occurs in a member or friend of class N, or in a member or friend of a class P derived from N, where m as a member of P is public, private, or protected

вторник, 9 октября 2018 г.

Почему в C# отказались от множественного наследования классов?

Друзья, объясните или скиньте ссылки, где можно найти ответ на вопрос "Почему в C# отказались от множественного наследования классов ?"


Ответ

TL;DR: добавление множественно наследования добавляет много сложностей, и мало выгоды от использования.

перевод ответа Chris Brumme на вопрос Why doesn’t C# support multiple inheritance?
Есть несколько причин почему мы не реализовали множественное наследование реализации напрямую. (Как вы знаете, мы поддерживаем множественное наследование интерфейсов). Однако, я должен обратить внимание на то, что компиляторы могут создавать множественное наследование для их типов внутри CLR. Есть несколько проблем, если идти этим путем: результат непроверяем, отсутствие взаимодействия с другими языками через CLS. Техника для создания некоторых VTables в RVA-шных статических полях. Для того, чтобы адреса управляемых методов (которые, возможно, даже не были обработаны JIT), вы используете конструкцию VTFixup. Эта конструкция представляет собой таблицу триплетов. Триплеты состоят из маркера на управляемый метод, адреса в вашем образе, который должен будет зафиксироваться (в этом случае, слот в VTable вы создаете в RVA-шных статических полях), и некоторых флагов. Возможные флаги описаны в corhdr.h. Они позволяют указать размер указателей: 32 бита vs 64 бита, контролировать виртуальное поведение, а также есть ли какой-нибудь режим обратного PInvoke, который следует применять в виде отложенных вычислений, что в итоге передаются в управляемый метод. Если мы выполняем переход unmanaged->managed, мы так же можем контролировать какой AppDomain будет выбран, чтобы отправить вызов. Есть несколько причин, почему мы не предоставили продуманную, поддающуюся проверке, CLS-совместимую версию множественного наследования реализации: Разные языки имеют разные ожидания того, как работает множественное наследование. Например, как решаются конфликты и будут ли дубликаты объединены или отброшены. Прежде чем мы сможем реализовать множественное наследование в CLR, мы должны сделать обзор всех языков, определить общие понятия, и решить как выразить их нейтральным способом. Мы должны решить будет ли множественное наследование входить в CLS и что это будет означать для языков, которые не хотят поддерживать эту концепцию (например VB.NET). Конечно это наше дело в CLR, но мы пока не нашли время сделать это для множественного наследования. Мест, где хорошо подходит множественное наследование, на самом деле очень мало. Во многих случаях работает множественное наследование интерфейсов. В других случаях можно использовать инкапсуляцию и делегирование. Если бы мы добавили другую конструкцию, например mixins, что было бы более мощным? Множественное наследование реализации добавляет сложности для реализации. Эта сложность влияет на кастинг, сериализацию, доступ к полям рефлексию и возможно многие другие места. Все еще не совсем ясно, что эта возможность окупит себя. Это то о чем мы часто спрашиваем себя. Это нечто, что мы еще не до конца проверили. Но мое нутро говорит, что после глубокого анализа мы решим не добавлять эту особенность.