Множественное наследование и виртуальные функции

следующий код:

struct interface_base
{
    virtual void foo() = 0;
};

struct interface : public interface_base
{
    virtual void bar() = 0;
};

struct implementation_base : public interface_base
{
    void foo();
};

struct implementation : public implementation_base, public interface
{   
    void bar();
};

int main()
{
    implementation x;
}

не удается скомпилировать со следующими ошибками:

test.cpp: In function 'int main()':
test.cpp:23:20: error: cannot declare variable 'x' to be of abstract type 'implementation'
test.cpp:16:8: note:   because the following virtual functions are pure within 'implementation':
test.cpp:3:18: note:    virtual void interface_base::foo()

Я поиграл с ним и понял, что создание виртуальных наследий "interface -> interface_base" и "implementation_base -> interface_base" устраняет проблему, но я не понимаю, почему. Кто-нибудь может объяснить, что происходит?

p.s. Я специально опустил виртуальные деструкторы, чтобы сделать код короче. Пожалуйста, не говори мне положите их, я уже знаю:)

3 ответов


вы два interface_base базовые классы в дереве наследования. Это означает, что вы должны предоставить два реализации foo(). И вызов любого из них будет действительно неудобным, требуя нескольких бросков для устранения двусмысленности. Обычно это не то, чего ты хочешь.

чтобы решить эту проблему, использовать виртуальное наследование:

struct interface_base
{
    virtual void foo() = 0;
};

struct interface : virtual public interface_base
{
    virtual void bar() = 0;
};

struct implementation_base : virtual public interface_base
{
    void foo();
};

struct implementation : public implementation_base, virtual public interface
{   
    void bar();
};

int main()
{
    implementation x;
}

при виртуальном наследовании в наследственной наследственности создается только один экземпляр рассматриваемого базового класса для все виртуальные упоминания. Таким образом, есть только один foo(), который может быть удовлетворен implementation_base::foo().

дополнительные сведения посмотреть этот предыдущий вопрос - ответы обеспечивают некоторые хорошие диаграммы, чтобы сделать это более ясным.


на обычная идиома C++ - это:

  • виртуальный наследование интерфейс классы
  • частный не виртуальный наследование реализация классы

в этом случае у нас было бы:

struct interface_base
{
    virtual void foo() = 0;
};

struct interface : virtual public interface_base
{
    virtual void bar() = 0;
};

struct implementation_base : virtual public interface_base
{
    void foo();
};

struct implementation : private implementation_base,
                        virtual public interface
{   
    void bar();
};

на implementation уникальный interface_base виртуальная база :

  • публично наследуется через interface: implementation -- публика-->interface -- public-->interface_base
  • в частном порядке унаследовано через implementation_base: implementation -- private-->implementation_base -- public-->interface_base

когда клиентский код выполняет одно из этих производных к базовым преобразованиям:

  • производные преобразования базового указателя,
  • привязка ссылки базового типа с инициализатором производного статического типа,
  • доступ к унаследованным членам базового класса через lvalue производных статических тип,

важно только то, что есть минимум один доступный путь наследования от производного класса к данному подобъектов базового класса; других недоступных путей просто игнорируются. Поскольку наследование базового класса здесь только виртуальное, существует только один субъект базового класса, поэтому эти преобразования никогда не бывают неоднозначными.

здесь преобразование из implementation to interface_base, всегда можно сделать с помощью клиентского кода через interface; другими недоступный путь не имеет значения. уникальный interface_base виртуальная база публично наследуется от implementation.

во многих случаях реализация классов (implementation, implementation_base) будет скрыт от клиентского кода: только указатели или ссылки на классы интерфейса (interface, interface_base) будет раскрыт.


для случая "решения" проблемы наследования алмазов решения, предлагаемые bdonlan являются действительными. Сказав это, вы можете избежать алмазной проблемы с дизайном. Почему каждый экземпляр данного класса должен рассматриваться как оба класса? Вы когда-нибудь передадите этот же объект классу, который говорит что-то вроде:

void ConsumeFood(Food *food);
void ConsumeDrink(Drink *drink);

class NutritionalConsumable {
  float calories() = 0;
  float GetNutritionalValue(NUTRITION_ID nutrition) = 0;
};
class Drink : public NutritionalConsumable {
  void Sip() = 0;
};
class Food : public NutritionalConsumable {
  void Chew() = 0;
};
class Icecream : public Drink, virtual public Food {};

void ConsumeNutrition(NutritionalConsumable *consumable) {
  ConsumeFood(dynamic_cast<Food*>(food));
  ConsumeDrink(dynamic_cast<Drink*>(drink));
}

// Or moreso
void ConsumeIcecream(Icecream *icecream) {
  ConsumeDrink(icecream);
  ConsumeFood(icecream);
}

конечно, было бы лучше в этом случае для Icecream просто реализовать NutritionalConsumable и предоставить GetAsDrink() и GetAsFood() метод, который вернет прокси, чисто ради появления либо пищи или питья. В противном случае это предполагает, что существует метод или объект, который принимает Food но как-то хочет позже увидеть его как Drink, что может быть достигнуто только с помощью dynamic_cast, и не нужно иметь дело с более подходящим дизайном.