Является виртуальным наследованием от чистых абстрактных классов (интерфейсов), необходимых
Почему в коде ниже компилятор жалуется, что PureAbstractBase
является двусмысленным базовым классом MultiplyInheritedClass
? Я понимаю, что у меня две копии PureAbstractBase
в MultiplyInheritedClass
и что FirstConreteClass
и SecondConreteClass
должны быть получены практически потому, что они представляют собой средний ряд алмаза (и это действительно исправляет проблему с помощью кода ниже). Но хотя у меня есть две копии интерфейса, почему код в MultiplyInheritedClass
не просто переопределяет оба и недвусмысленно выбирает класс интерфейса, определенный в MultiplyInheritedClass
?
#include <iostream>
using namespace std;
class PureAbstractBase {
public:
virtual void interface() = 0;
};
// I know that changing the following line to:
// class FirstConcreteClass : public virtual PureAbstractBase {
// fixes the problem with this hierarchy
class FirstConcreteClass : public PureAbstractBase {
public:
virtual void interface() { implementation(); }
private:
void implementation() { cout << "This is object FirstConcreteClass\n"; }
};
// I know that changing the following line to:
// class SecondConcreteClass : public virtual PureAbstractBase {
// fixes the problem with this hierarchy
class SecondConcreteClass : public PureAbstractBase {
public:
virtual void interface() { implementation(); }
private:
void implementation() { cout << "This is object SecondConcreteClass\n"; }
};
class MultiplyInheritedClass : public FirstConcreteClass,
public SecondConcreteClass {
public:
virtual void interface() { implementation(); }
private:
void implementation() { cout << "This is object MultiplyInheritedClass\n"; }
};
Кроме того, почему у меня нет проблем со следующей иерархией? В этом случае класс ConcreteHandler не имеет трех экземпляров AbstractTaggingInterface? Так почему же у него нет такой же проблемы, как в примере выше?
#include <iostream>
using namespace std;
class AbstractTaggingInterface {
public:
virtual void taggingInterface() = 0;
};
class FirstAbstractHandler : public AbstractTaggingInterface {
public:
virtual void taggingInterface() { cout << "FirstAbstractHandler\n"; }
virtual void handleFirst() = 0;
};
class SecondAbstractHandler : public AbstractTaggingInterface {
public:
virtual void taggingInterface() { cout << "SecondAbstractHandler\n"; }
virtual void handleSecond() = 0;
};
class ThirdAbstractHandler : public AbstractTaggingInterface {
public:
virtual void taggingInterface() { cout << "ThridAbstractHandler\n"; }
virtual void handleThird() = 0;
};
class ConcreteHandler : public FirstAbstractHandler,
public SecondAbstractHandler,
public ThirdAbstractHandler {
public:
virtual void taggingInterface() = { cout << "ConcreteHandler\n"; }
virtual void handleFirst() {}
virtual void handleSecond() {}
virtual void handleThird() {}
};
Я пытаюсь обвести вокруг себя все это, потому что недавно у меня был разговор с коллегой, где он утверждал, что если вы наследовали из чистых виртуальных классов (интерфейсов) без каких-либо элементов данных, тогда виртуальное наследование не было необходимым. Я думаю, что понимание того, почему пример бывшего кода не работает, и последнее делает долгий путь, чтобы получить это прямо в моей голове (и выяснить, что именно он имел в виду под своим комментарием). Заранее спасибо.
Ответы
Ответ 1
Вам нужно виртуальное наследование для преодоления двусмысленности бриллианта:
class FirstConcreteClass : public virtual PureAbstractBase { ... };
class SecondConcreteClass : public virtual PureAbstractBase { ... };
Долгосрочное объяснение: предположим, что у вас есть это:
// *** Example with errrors! *** //
struct A { virtual int foo(); };
struct B1 : public A { virtual int foo(); };
struct B2 : public A { virtual int foo(); };
struct C: public B1, public B2 { /* ... */ }; // ambiguous base class A!
int main() {
A * px = new C; // error, ambiguous base!
px->foo(); // error, ambiguous override!
}
Наследование виртуальной функции foo
неоднозначно, потому что оно происходит тремя способами: от B1
, от B2
и от A
. Диаграмма наследования образует "алмаз":
/-> B1 >-\
A-> ->C
\-> B2 >-/
Сделав наследование виртуальным, struct B1 : public virtual A;
и т.д., вы разрешаете любому базовому классу C*
вызывать правильный член:
struct A { virtual int foo(); };
struct B1 : public virtual A { virtual int foo(); };
struct B2 : public virtual A { virtual int foo(); };
struct C: public B1, public B2 { virtual int foo(); };
Мы должны также определить C::foo()
, чтобы это имело смысл, так как иначе C
не имел бы четко определенного члена foo
.
Еще несколько деталей: предположим, что теперь у нас есть фактически фактически наследующий класс C
, как указано выше. Мы можем получить доступ ко всем различным виртуальным членам по желанию:
int main() {
A * pa = new C;
pa->foo(); // the most derived one
pa->A::foo(); // the original A foo
B1 * pb1 = new C;
pb1->foo(); // the most derived one
pb1->A::foo(); // A foo
pb1->B1::foo(); // B1 foo
C * pc = new C;
pc->foo(); // the most derived one
pc->A::foo(); // A foo
pc->B1::foo(); // B1 foo
pc->B2::foo(); // B2 foo
pc->C::foo(); // C foo, same as "pc->foo()"
}
Обновление. Как говорит Дэвид в комментарии, важным моментом здесь является то, что промежуточные классы B1
и B2
наследуют фактически так, что дальнейшие классы (в данном случае C
) могут унаследовать от них, одновременно сохраняя наследование от A
недвусмысленное. Извините за первоначальную ошибку и спасибо за исправление!
Ответ 2
Ваш первый пример завершился неудачно, потому что компилятор не может устранить неоднозначность между тремя реализациями implementation()
. Вы переопределяете этот метод в MultiplyInheritedClass
, который фактически переопределяет как FirstConcreteClass::implementation
, так и SecondConcreteClass::implementation
(один раз виртуальный, всегда виртуальный). Однако оба виртуальных вызова все еще существуют в интерфейсе MultiplyInheritedClass
, что делает вызов неоднозначным на сайте вызова.
Причина, по которой ваш пример работает без наследования virtual
, заключается в том, что не существует противоречивой реализации общего базового класса. Другими словами:
class Base
{
public:
void DoSomething() {
std::cout << "TADA!";
}
}
class One : public Base
{
//...
}
class Two : public Base
{
//...
}
class Mixed : public One, public Two
{
//...
}
int main()
{
Mixed abc;
abc.DoSomething(); //Fails because the compiler doesn't know whether to call
// One::DoSomething or Two::DoSomething, because they both
// have implementations.
//In response to comment:
abc.One::DoSomething(); //Succeeds! You removed the ambiguity.
}
Поскольку ваш пример имеет все чистые виртуальные функции, нет много реализаций, которые компилятор должен устранить. Следовательно, существует только одна реализация, и вызов однозначен.
Ответ 3
Я пробовал оба кода вопроса, и они отлично работали при создании экземпляра объекта многоуровневого класса. Он не работал только с полиморфизмом, как это, например:
PureAbstractBase* F;
F = new MultiplyInheritedClass();
И причина ясна: он не знает, к какой копии базового класса Аннотация он должен быть связан (извините за плохие выражения, я понимаю идею, но не могу ее выразить). И так как вложение virtaully делает только одну копию в производном классе, тогда это прекрасно.
Также код Billy ONeal не совсем понятен, что мы должны помещать вместо комментариев?
Если мы разместим:
public:
void DoSomething()
{ std::cout << "TADA!"; }
он отлично работает, поскольку нет виртуальности.
Я работаю над Visual Studio 2008.
Ответ 4
Почему бы не сделать это так (предложено в запись блога Бенджамина Супника):
#include <iostream>
class PureAbstractBase {
public:
virtual void interface() = 0;
};
class FirstConcreteClass : public PureAbstractBase {
public:
virtual void interface() { implementation(); }
private:
void implementation() { std::cout << "Fisrt" << std::endl; }
};
class SecondConcreteClass : public PureAbstractBase {
public:
virtual void interface() { implementation(); }
private:
void implementation() { std::cout << "Second" << std::endl; }
};
class MultiplyInheritedClass : public FirstConcreteClass,
public SecondConcreteClass
{
public:
virtual void interface() { implementation(); }
private:
void implementation() { std::cout << "Multiple" << std::endl; }
};
int main() {
MultiplyInheritedClass mic;
mic.interface();
FirstConcreteClass *fc = &mic; //disambiguate to FirstConcreteClass
PureAbstractBase *pab1 = fc;
pab1->interface();
SecondConcreteClass *sc = &mic; //disambiguate to SecondConcreteClass
PureAbstractBase *pab2 = sc;
pab2->interface();
}
который дает:
Multiple
Multiple
Multiple
Таким образом:
- не задействованы виртуальные базы (вам они действительно нужны?)
- вы можете вызвать функцию overriden через экземпляр
MultiplyInheritedClass
- неоднозначность удаляется двухэтапным преобразованием