Ответ 1
Взгляните на часто задаваемые вопросы по С++ со следующего момента: https://isocpp.org/wiki/faq/multiple-inheritance#mi-diamond
В нем подробно описывается "страшный бриллиант" и виртуальное наследование.
Я хочу иметь интерфейс IA и другой, который расширяет его IB.
A затем реализует IA, а B наследует A, а также реализует IB.
Однако, когда компиляция B получает ошибки, говорящие, что материал IA равен undefined, хотя A определил все: (
class IA
{
public:
virtual ~IA(){}
virtual void foo()=0;
};
class IB : public IA
{
public:
virtual void bar()=0;
};
class A : public IA
{
public:
A();
void foo();
};
class B : public A, public IB
{
public:
B();
void bar();
};
ошибка C2259: 'B': не может создать экземпляр абстрактного класса
за следующих членов:
'void IA:: foo (void)': is abstract
Взгляните на часто задаваемые вопросы по С++ со следующего момента: https://isocpp.org/wiki/faq/multiple-inheritance#mi-diamond
В нем подробно описывается "страшный бриллиант" и виртуальное наследование.
(Нет, A
ничего не определяет. Вы объявили A()
и void foo()
, но вы не определили их.)
Реальная проблема - ваше множественное наследование.
B
/ \
A IB
/ \
IA IA
Как вы можете видеть, в вашем дереве наследования есть две "версии" IA
, и только A
реализует void IA::foo
(хотя, как я уже отмечал выше, это даст вам ошибку компоновщика, как вы это делали 't определить реализацию).
void IB::IA::foo()
остается нереализованным; таким образом, B
сам "наследует абстрактность", и вы не можете его создать.
B
также потребуется реализовать void foo()
, или вы можете использовать виртуальное наследование, чтобы взломать его.
Это реальный полностью оправданный случай для виртуального наследования, который не всегда является дизайнерским запахом.
В случае интерфейса/реализации параллельных иерархий каждое отношение наследования формы "любой класс → интерфейс" должно быть помечено как виртуальное: class A : public virtual IA
; class B : public virtual IB, public A
и class IB : public virtual IA
.
Этот способ, вероятно (зависимый от реализации, но по крайней мере концептуально), дополнительный указатель хранится в каждом фактически производном классе, чтобы знать, где он должен найти свою виртуальную базу. Класс A
, B
и IB
будет иметь указатель на IA
, а класс B
будет иметь указатель на IB
. Каждая виртуальная база также будет иметь свою собственную таблицу vtable.
Точка зрения "страшный бриллиант" является графически приятной, но дело в том, что производные классы должны знать, где находится базовый класс, который они будут использовать, и именно там происходит виртуальное наследование. Очевидно, вам нужна виртуальная таблица для интерфейса здесь, и виртуальное наследование позволяет вам делать именно это.
Затем вы можете продолжить параллелизм и добавить дополнительные классы C
, IC
и т.д., просто запомните:
Всякий раз, когда вы наследуете интерфейс, наследуйте фактически.
Кстати, классы COM имеют схожую конструкцию.
У вас есть два копии foo(), один из которых унаследован от IB:: IA и один из A:: IA. Только один из них имеет не абстрактную версию в A.