Виртуальное наследование - проблема с бриллиантами - что происходит на самом деле

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

«Что на самом деле означает размещение виртуального рядом с базовым классом, от которого вы унаследуете?»

class A { public: void Foo() {} };
class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};

Я хотел знать, правильно ли я понимаю. В заявлении

  class D : public B, public C {}

Компилятор перейдет к первому базовому классу B и заметит, что он фактически наследуется от class A. Компилятор проверит, присутствует ли экземпляр класса A, если нет, он создаст экземпляр A, производный от B. Затем компилятор перейдет к классу C и заметит, что он фактически наследуется от класса A. Однако, поскольку он наследуется виртуально C и экземпляр A уже присутствует, он не будет включать новый экземпляр. Таким образом решается алмазная проблема. Это понимание правильное?


person Rajeshwar    schedule 02.04.2014    source источник
comment
Да, ваше понимание правильное. Прекрасное объяснение по этой теме можно найти в книге Липпмана «Модель внутреннего объекта C ++».   -  person Mantosh Kumar    schedule 02.04.2014


Ответы (1)


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

В вашем случае ваш экземпляр D будет композицией из C, B и одного экземпляра A. Поскольку как A, так и B могут участвовать в разных сценариях с разными композициями, где A фактически наследуется многими классами (может быть, даже больше классов, чем A и B), неясно, как расположить, например, строительные блоки экземпляра D в объем памяти. По этой причине в коде, скомпилированном для классов B и C, обычно используются служебные указатели или смещения (1 указатель на экземпляр, один для B и один для C), которые указывают на экземпляр A, который является общим для кода различных классов, наследующих фактически из класса A. Это занимает дополнительное место в вашем экземпляре D и делает ваш код менее эффективным, играя с указателями.

Еще одна неприятная деталь реализации заключается в том, что в случае вашего класса D, когда вы создаете / инициализируете экземпляр D, оба ваших экземпляра B и C "гоняются" за вызовом конструктора класса A из своих конструкторов. По этой причине ваш экземпляр D также будет содержать переменную типа bool, которая указывает, вызвал ли кто-то уже ctor A из его конструктора. Один из операторов B и C победит и установит эту переменную bool в значение true, поэтому следующие попытки, исходящие от операторов других «виртуальных потомков», просто завершатся сбоем после проверки этого конкретного значения bool.

Виртуальное наследование просто некрасиво, и его следует избегать, когда это возможно.

person pasztorpisti    schedule 02.04.2014
comment
Здесь нет гонок - конструкторы всегда вызываются строго по порядку снизу вверх и слева направо (поэтому построение D всегда будет включать сначала построение A, затем B, затем C). Кроме того, разумные реализации сохранят базовое смещение A в таблице vtable и не будут использовать дополнительную память для каждого экземпляра. - person Chris Dodd; 03.04.2014
comment
@ChrisDodd, что имеет смысл, довольно давно я проверил эту реализацию, и она фактически сохранила указатель в экземпляре. Тем не менее, даже реализация vtable имеет свои недостатки, большее количество операций поиска в обмен на более эффективное использование памяти, но это по-прежнему не делает виртуальное наследование привлекательным. Говоря о гонках, я имел в виду, что в реализации, которую я проверил, все производные ctors фактически вызвали ctor для A, но только первый вызов достиг своей цели. Вы правы в том, что «гонки» могут быть здесь не совсем подходящим словом, поскольку это звучит так, как если бы у нас возникла проблема, связанная с многопоточностью. :-) - person pasztorpisti; 03.04.2014
comment
Что касается сложных сценариев, в которых виртуальное наследование действительно разумно: посмотрите этот ответ и следующее: вопрос я разместил. - person Oguk; 20.06.2014