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. Это приведет к включению 2 копий класса A в vtable.
Чтобы решить эту проблему, нам нужно виртуальное наследование. Это класс А, который необходимо наследовать виртуально. Итак, это решит проблему:
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.
Что ж, самое замечательное в Dreaded Diamond - это то, что когда он возникает, это ошибка. Лучший способ избежать - заранее выяснить структуру наследования. Например, в одном проекте, над которым я работаю, есть зрители и редакторы. Редакторы являются логическими подклассами средств просмотра, но поскольку все средства просмотра являются подклассами - TextViewer, ImageViewer и т. Д., Editor не является производным от Viewer, что позволяет последним классам TextEditor, ImageEditor избегать ромба.
В случаях, когда ромба не избежать, используется виртуальное наследование. Однако самая большая проблема с виртуальными базами заключается в том, что конструктор виртуальной базы должен вызываться наиболее производным классом, что означает, что производный класс практически не имеет контроля над параметрами конструктора. Кроме того, наличие виртуальной базы имеет тенденцию приводить к снижению производительности / площади при прохождении по цепочке, хотя я не думаю, что есть много штрафов за большее, чем первое.
Кроме того, вы всегда можете использовать ромб, если четко указываете, какую базу вы хотите использовать. Иногда это единственный выход.
Я бы предложил лучший дизайн класса. Я уверен, что есть проблемы, которые лучше всего решить с помощью множественного наследования, но сначала проверьте, есть ли другой способ.
Если нет, используйте виртуальные функции / интерфейсы.
Используйте наследование путем делегирования. Тогда оба класса будут указывать на базовый A, но должны реализовать методы, которые перенаправляют на A. Это имеет побочный эффект превращения защищенных членов A в «частные» члены в B, C и D, но теперь вы этого не делаете. нужен виртуальный, а у тебя нет алмаза.