2012-01-02 9 views
16

Следующий код:множественное наследование и чистые виртуальные функции

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

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

+0

см. Также: http://stackoverflow.com/questions/457609/diamond-inheritance-and-pure-virtual-functions – bdonlan

ответ

14

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

Для получения дополнительной информации, see this prior question - ответы содержат некоторые интересные диаграммы, чтобы сделать это более понятным.

+0

+1: Ударьте меня к нему. –

+0

Есть ли способ сказать «использовать реализацию из« реализации_базы »для обеих реализаций' foo() ', не используя виртуальное наследование? – HighCommander4

+0

@ HighCommander4, вы можете получить интерфейс от 'implementation_base' или наоборот, тем самым устраняя алмаз и создавая линейное дерево наследования. Кроме этого, нет, но если вы хотите скрыть это, вы можете сделать пустые обертки 'interface_base' и' interface', которые фактически получаются из класса интерфейса _real_. – bdonlan

2

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

+1

Ваш пример отличается от моего.В моем случае 'реализация' происходит от' interface', потому что он будет использоваться полиморфно как 'interface *', и он происходит от 'implementation_base', чтобы повторно использовать реализацию' interface_base' части 'interface'. – HighCommander4

+1

Итак, кому нужно видеть это как «реализация_база»? А кто общается с 'интерфейсом', но не' interface_base'? Каково было мнение, что «реализация_база» наследует от 'interface_base', а не« интерфейс »? Хотя мои вопросы довольно абстрактны, вы решаете реальный дизайн системы или просто проблему с алмазом? –

+0

Никому не нужно рассматривать его как 'implementation_base', я мог бы просто получить его из' interface' и продублировать реализацию внутри 'implementation_base', но я не хочу дублировать вещи. – HighCommander4

8

++ идиома обычного C является:

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

I п в этом случае мы имеем:

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 производного статического типа,

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

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

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

+0

Я бы не сказал, что« публичное виртуальное наследование для классов интерфейса »является« (или) обычной идиомой C++ ». Может быть, так и должно быть, но из того, что я могу сказать, наверняка нет. –

+0

@PaulGroke Hum .. вы можете быть правы. Правильная идиома? Рекомендуемая идиома? – curiousguy

+0

Я написал «возможно, это должно быть», потому что я честно не уверен, что нужно. Я почти никогда не использую виртуальное наследование (на самом деле я не уверен, что я * когда-либо использовал его в производственном коде). Поэтому я не знаю, какие проблемы могут возникнуть, и какие из этих проблем также влияют на виртуальное наследование с чистыми интерфейсами (т. Е. Тривиальный ctor, тривиальный dtor, без данных). –