Это основной вариант использования для виртуального наследования.
Несмотря на все стигмы, которые сопровождают множественное и виртуальное наследование, нет особых проблем, когда наши интерфейсы (без элементов данных) фактически наследуются. Вот суть:
class BaseIf
{
public:
virtual ~BaseIf() {}
virtual std::string getName() = 0;
};
class IntIf : public virtual BaseIf
{
public:
virtual ~IntIf() {}
virtual int getValue() = 0;
};
class BaseImpl : public virtual BaseIf
{
public:
std::string getName () override { return "whoa dude"; }
};
class IntImpl : public virtual IntIf, public BaseImpl
{
public:
int getValue() override { return 42; }
};
full demo
При более глубокой иерархии, вероятно, придется виртуально наследовать и классы реализации, что не очень удобно, но все жевыполнимо.
Альтернативой виртуальному наследованию реализации было бы разделение реализации на слой «строительных блоков» и конечный уровень. Строительные блоки автономны и не наследуют другие строительные блоки. (Они могут наследовать интерфейсы). Конечные классы наследуют строительные блоки, но не другие конечные классы.
class BaseBlock : public virtual BaseIf
{
public:
std::string getName () override { return "whoa dude"; }
};
class IntBlock : public virtual IntIf
{
public:
int getValue() override { return 42; }
};
class BaseImpl : public BaseBlock {};
class IntImpl : public BaseBlock, public IntBlock {};
full demo
Необходимо внести изменения в интерфейсы, если не было виртуального наследованияв иерархии. Эти изменения, однако, прозрачны (код клиента не нужно изменять, только перекомпилировать) и, вероятно, в любом случае выгодны.
Без виртуального наследования придется прибегнуть к множеству шаблонов.
class BaseBlock // no base class!
{
public:
virtual std::string getName () { return "whoa dude"; }
};
class BaseImpl : public BaseIf, public BaseBlock
{
public:
// oops, getName would be ambiguous here, need boplerplate
std::string getName () override { return BaseBlock::getName(); }
};