http://en.wikipedia.org/wiki/Diamond_problem
Я знаю, что это значит, но какие шаги я могу предпринять, чтобы избежать этого?
http://en.wikipedia.org/wiki/Diamond_problem
Я знаю, что это значит, но какие шаги я могу предпринять, чтобы избежать этого?
Практический пример:
class A {};
class B : public A {};
class C : public A {};
class D : public B, public C {};
Обратите внимание, как класс D наследуется как от B, так и от C. Но оба B и C наследуют от A. Это приведет к тому, что в vtable будут включены 2 копии класса A.
Чтобы решить эту проблему, нам нужно виртуальное наследование. Это класс А, который должен быть фактически унаследован. Таким образом, это устранит проблему:
class A {};
class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};
виртуальное наследование. Это то, что там есть.
Я бы придерживался только множественного наследования интерфейсов. Хотя множественное наследование классов иногда привлекательно, оно также может быть запутанным и болезненным, если вы регулярно полагаетесь на него.
Наследование - сильное, сильное оружие. Используйте его только тогда, когда вам это действительно нужно. Раньше наследование бриллиантов было признаком того, что я собирался далеко ходить по классификации, заявив, что пользователь является "сотрудником", но они также являются "слушателем виджета", но также...
В этих случаях легко ударить несколько проблем наследования.
Я решил их, используя композицию и указатели обратно владельцу:
До:
class Employee : public WidgetListener, public LectureAttendee
{
public:
Employee(int x, int y)
WidgetListener(x), LectureAttendee(y)
{}
};
После:
class Employee
{
public:
Employee(int x, int y)
: listener(this, x), attendee(this, y)
{}
WidgetListener listener;
LectureAttendee attendee;
};
Да, права доступа разные, но если вы можете избежать такого подхода, не дублируя код, это лучше, потому что он менее мощный. (Вы можете сохранить питание, если у вас нет альтернативы.)
Эта ссылка ответит на ваш вопрос подробно:
class A {};
class B : public A {};
class C : public A {};
class D : public B, public C {};
В этом атрибуты класса A повторяются дважды в классе D, что увеличивает использование памяти... Поэтому для сохранения памяти мы создаем виртуальный атрибут для всех унаследованных атрибутов класса A, которые хранятся в Vtable.
Хорошо, великая вещь о Страшном Британии - это то, что это ошибка, когда это происходит. Лучший способ избежать - заранее выяснить структуру наследования. Например, в одном проекте, в котором я работаю, есть зрители и редакторы. Редакторы - это логические подклассы Viewers, но поскольку все зрители являются подклассами - TextViewer, ImageViewer и т.д., Редактор не выводит из Viewer, что позволяет конечным классам TextEditor, ImageEditor избегать алмаза.
В тех случаях, когда алмаз нельзя избежать, используя виртуальное наследование. Однако самое большое предостережение с виртуальными базами заключается в том, что конструктор виртуальной базы должен вызываться самым производным классом, а это означает, что класс, который получает практически не имеет никакого контроля над параметрами конструктора. Кроме того, наличие виртуальной базы имеет тенденцию к штрафу за производительность/пространство при кастинге через цепочку, хотя я не верю, что существует большая часть штрафа за более чем первое.
Кроме того, вы всегда можете использовать алмаз, если вы четко указываете, какую базу вы хотите использовать. Иногда это единственный способ.
Я бы предложил лучший дизайн класса. Я уверен, что есть некоторые проблемы, которые лучше всего решаются с помощью множественного наследования, но проверьте, есть ли другой способ.
Если нет, используйте виртуальные функции/интерфейсы.
Использовать наследование путем делегирования. Затем оба класса будут указывать на базу A, но должны реализовать методы, которые перенаправляют на A. Он имеет побочный эффект превращения защищенных членов A в "private" членов в B, C и D, но теперь вы не нужен виртуальный, и у вас нет бриллианта.