Это упрощение некоторого реального кода, и настоящая ошибка, которую я допустил, когда я не осознавал, что кто-то другой уже реализовал Foo и извлек из него.
#include <iostream>
struct Base {
virtual ~Base() { }
virtual void print() = 0;
};
struct OtherBase {
virtual ~OtherBase() { }
};
struct Foo : public Base { // better to use virtual inheritance?
virtual void print() { std::cout << "Foo" << std::endl; };
};
struct Bar : public Base { // better to use virtual inheritance?
virtual void print() { std::cout << "Bar" << std::endl; };
};
// The design is only supposed to implement Base once, but I
// accidentally created a diamond when I inherited from Bar also.
class Derived
: public OtherBase
, public Foo
, public Bar // oops.
{
};
int main() {
Derived d;
OtherBase *pO = &d;
// cross-casting
if (Base *pBase = dynamic_cast<Base *>(pO))
pBase->print();
else
std::cout << "fail" << std::endl;
}
РЕДАКТИРОВАТЬ : чтобы избавить вас от необходимости запускать этот код ...
- При запуске как есть, выводится сообщение «ошибка» (нежелательно, трудно отладить).
- Если вы удалите строку, помеченную «oops», будет напечатано «Foo» (желаемое поведение).
- Если вы оставите «упс» и сделаете два наследования виртуальными, он не скомпилируется (но, по крайней мере, вы знаете, что исправить).
- Если вы удалите «упс» и сделаете их виртуальными, он скомпилируется и выведет «Foo» (желаемое поведение).
При виртуальном наследовании результаты либо хорошие, либо ошибка компилятора. Без виртуального наследования результаты либо хорошие, либо необъяснимые, трудно отлаживаемые ошибки времени выполнения.
Когда я реализовал Bar, который в основном дублировал то, что уже делал Foo, это привело к сбою динамического приведения, что означало плохие вещи в реальном коде.
Сначала я был удивлен, что не было ошибки компилятора. Затем я понял, что виртуального наследования не существует, что могло бы вызвать ошибку «нет уникального окончательного переопределения» в GCC. Я намеренно решил не использовать виртуальное наследование, поскольку в этом дизайне не должно быть никаких бриллиантов.
Но если бы я использовал виртуальное наследование при наследовании от Base, код работал бы так же хорошо (без моих опций), и меня бы предупредили о бриллианте во время компиляции и необходимости отслеживать ошибку при запуске время.
Итак, вопрос в том, считаете ли вы допустимым использование виртуального наследования для предотвращения повторения подобной ошибки в будущем? Здесь нет веских технических причин (которые я вижу) для использования виртуального наследования, поскольку в дизайне никогда не должно быть ромба. Это было бы только для обеспечения соблюдения этого проектного ограничения.