Как я могу избежать Алмаза смерти при использовании множественного наследования?

http://en.wikipedia.org/wiki/Diamond_problem

Я знаю, что это означает, но что я могу предпринять, чтобы этого избежать?


person ilitirit    schedule 26.09.2008    source источник
comment
Я хочу сказать, что не используйте множественное наследование, но это всего лишь кадры. Я тоже хотел бы увидеть хороший ответ на этот вопрос.   -  person Chris Charabaruk    schedule 26.09.2008
comment
Diamond of Death немного драматичен. Что именно вы хотите знать.   -  person Martin York    schedule 26.09.2008
comment
Он широко известен как Смертельный алмаз смерти. Погугли это.   -  person ilitirit    schedule 26.09.2008
comment
Google сообщает мне, что это обычно известно как проблема алмазов, за исключением сообщества Java, где используется более резкий термин, чтобы оправдать, почему Java решает проблему, запрещая ее.   -  person wolfgang    schedule 15.12.2011
comment
Здесь смерти нет. И виртуальное, и стандартное наследование находят свое применение (правда, очень редко).   -  person Alexandre C.    schedule 09.03.2012


Ответы (8)


Практический пример:

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 {};
person Mark Ingram    schedule 26.09.2008
comment
Это только позволяет избежать двойного нахождения A в памяти, это не избавляет от проблем, вызванных алмазом. См. tinyurl.com/abtjcb; как реализовать getDepartment, чтобы он всегда возвращал то, что нужно? Вы не можете! Ваш дизайн ошибочен. См. tinyurl.com/ccjnk6. - person Mecki; 25.02.2009
comment
Вот для чего нужен прицел. В качестве альтернативы вы можете использовать оператор using в классе D. - person Mark Ingram; 26.02.2009
comment
Разве этот ответ действителен только для классов, которые вы контролируете? Если B и C находятся в библиотеке, предоставленной кем-то другим, или они являются частью базы кода, вы не можете изменить это «решение», оно вообще не работает. Это также противоречит всему принципу ООП, согласно которому базовый класс не должен иметь отношения к производным классам, но здесь B и C внезапно должны измениться из-за некоторого класса D, который был добавлен позже в тот же день. - person jbx; 18.10.2015
comment
У меня не работает. Получена ошибка компоновщика vtable. См. Мой ответ ниже. - person Balazs Kelemen; 05.02.2021

виртуальное наследование. Вот для чего он нужен.

person eduffy    schedule 26.09.2008
comment
Где в иерархии наследования? - person ilitirit; 26.09.2008
comment
Если у вас есть B и C, производные от A, и D, производные от B и C, то B и C должны оба объявить A как виртуальную базу. В частности, каждый экземпляр виртуального наследования одного и того же класса сворачивается в один класс. Любые невиртуальные из них не будут свернуты, что приведет к повторению ромба. - person coppro; 26.09.2008
comment
Хотя виртуальное наследование - это возможность обойти проблему Diamond of Death, я думаю, что есть более эффективные способы решения этой проблемы. А именно, наследование от абстрактных базовых классов (классов интерфейса) вместо наследования от нескольких конкретных классов. - person Nick Haddad; 26.09.2008

Я бы придерживался только множественного наследования интерфейсов. Хотя множественное наследование классов иногда является привлекательным, оно также может сбивать с толку и вызывать боль, если вы регулярно на него полагаетесь.

person Bob Somers    schedule 26.09.2008
comment
@Arafangion C ++ имеет интерфейсы, хотя это не языковая конструкция, как, например, в Java. Вместо этого это просто виртуальные базовые классы. - person jlh; 11.06.2012
comment
@jlh: Я согласен с этим, хотя я бы сказал, что, хотя сам C ++ не имеет интерфейсов, язык позволяет вам их реализовать. - person Arafangion; 12.06.2012
comment
Я никогда не встречал кого-то, кто не понимал бы, что это будет означать наследование чистых абстрактных классов в этом контексте. - person BlueTrin; 08.11.2014

Наследование - сильное, сильное оружие. Используйте его только тогда, когда вам это действительно нужно. В прошлом алмазное наследование было признаком того, что я зашел слишком далеко с классификацией, говоря, что пользователь является «сотрудником», но он также является «слушателем виджета», но также ...

В этих случаях легко решить проблемы множественного наследования.

Я решил их, используя композицию и указатели на владельца:

До:

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;
};

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

person Community    schedule 27.09.2008
comment
И что вы сделали, так это то, что вы значительно увеличили использование памяти. Нет, спасибо. - person luke1985; 03.11.2015
comment
Состав против наследования. Борьба! - person Alexander Shishenko; 03.02.2016

class A {}; 
class B : public A {}; 
class C : public A {}; 
class D : public B, public C {};

В этом случае атрибуты класса A повторяются дважды в классе D, что увеличивает использование памяти ... Итак, чтобы сэкономить память, мы создаем виртуальный атрибут для всех унаследованных атрибутов класса A, которые хранятся в таблице Vtable.

person NItish    schedule 15.07.2012

Что ж, самое замечательное в Dreaded Diamond - это то, что когда он возникает, это ошибка. Лучший способ избежать - заранее выяснить структуру наследования. Например, в одном проекте, над которым я работаю, есть зрители и редакторы. Редакторы являются логическими подклассами средств просмотра, но поскольку все средства просмотра являются подклассами - TextViewer, ImageViewer и т. Д., Editor не является производным от Viewer, что позволяет последним классам TextEditor, ImageEditor избегать ромба.

В случаях, когда ромба не избежать, используется виртуальное наследование. Однако самая большая проблема с виртуальными базами заключается в том, что конструктор виртуальной базы должен вызываться наиболее производным классом, что означает, что производный класс практически не имеет контроля над параметрами конструктора. Кроме того, наличие виртуальной базы имеет тенденцию приводить к снижению производительности / площади при прохождении по цепочке, хотя я не думаю, что есть много штрафов за большее, чем первое.

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

person coppro    schedule 26.09.2008

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

Если нет, используйте виртуальные функции / интерфейсы.

person user17720    schedule 26.09.2008
comment
сначала проверьте, есть ли другой способ Почему? - person curiousguy; 01.11.2011

Используйте наследование путем делегирования. Тогда оба класса будут указывать на базовый A, но должны реализовать методы, которые перенаправляют на A. Это имеет побочный эффект превращения защищенных членов A в «частные» члены в B, C и D, но теперь вы этого не делаете. нужен виртуальный, а у тебя нет алмаза.

person Lee Louviere    schedule 11.02.2011