Множественное наследование и чисто виртуальные функции - PullRequest
16 голосов
/ 02 января 2012

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

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_base'and' creation_base -> interface_base 'наследует виртуально, решает проблему, но я не понимаю, почему.Может кто-нибудь объяснить, что происходит?

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

Ответы [ 3 ]

14 голосов
/ 02 января 2012

У вас есть два 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().

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

11 голосов
/ 02 августа 2012

обычная идиома 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 --public -> interface --public -> interface_base
  • конфиденциально наследуется через implementation_base: implementation --private -> implementation_base --public -> interface_base

Когда клиентский код выполняет одно из следующих преобразований в базовые:

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

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

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

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

2 голосов
/ 02 января 2012

В случае «решения» проблемы наследования алмазов, решения, предлагаемые 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, и это не обязательно должно быть в случае с болеесоответствующий дизайн.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...