Подтвердить что ты не робот

Почему виртуальное наследование необходимо указывать в середине иерархии алмазов?

У меня есть алмазная иерархия классов:

    A
  /   \
 B     C
  \   /
    D

Чтобы избежать двух копий A в D, нам нужно использовать виртуальное наследование в B и C.

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

Вопрос: Почему виртуальное наследование должно выполняться в B и C, хотя двусмысленность в D? Это было бы более интуитивно, если бы оно было в D.

Почему эта функция разработана таким же образом комитетом по стандартизации? Что мы можем сделать, если классы B и C поступают из сторонней библиотеки?

EDIT: Мой ответ состоял в том, чтобы указать классы B и C, что они не должны вызывать конструктор всякий раз, когда его производный объект создается, поскольку он будет вызываться D.

4b9b3361

Ответ 1

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

Предположим, что С++ был спроектирован таким образом, чтобы разрешить проблему с алмазом, вы фактически наследовали B и C в D, а не фактически наследовали A в B и C. Теперь, какова будет макет объекта для B и C? Ну, если никто никогда не пытается наследовать от них, то у каждого будет своя копия A и может использовать стандартную оптимизированную компоновку, где B и C имеют A на своей базе. Однако, если кто-то фактически наследует от B или C, тогда макет объекта должен быть другим, потому что двум придется делиться своей копией A.

Проблема заключается в том, что когда компилятор сначала видит B и C, он не может знать, будет ли кто-то наследовать от них. Следовательно, компилятор должен был бы отказаться от более медленной версии наследования, используемой в виртуальном наследовании, а не более оптимизированной версии наследования, которая включена по умолчанию. Это нарушает принцип С++ "не платите за то, для чего вы не используете" (принцип с нулевым уровнем дохода), где вы платите только за языковые функции, которые вы явно используете.

Ответ 2

Почему виртуальное наследование должно выполняться в B и C, хотя двусмысленность в D? Это было бы более интуитивно, если бы оно было в D.

В вашем примере B и C используют virtual специально, чтобы попросить компилятор обеспечить наличие только одной копии A. Если они этого не сделали, они эффективно говорят: "Мне нужен мой собственный базовый класс, я не собираюсь делиться им с каким-либо другим производным объектом". Это может иметь решающее значение.

Пример того, что вы не хотите делиться виртуальным базовым классом

Если A был каким-то контейнером, B был получен из него и сохранил определенный тип объекта - скажем, "Bat", а C хранит "Cat". Если D ожидает, что B и C независимо предоставят информацию о населении Bats and Cats, они были бы очень удивлены, если бы операция C сделала что-то с Bats или B-операция сделала что-то с кошками. /p >

Пример наличия виртуального базового класса

Говорить D необходимо предоставить доступ к некоторым функциям или элементам данных, которые находятся в A, например "A:: x"... если A наследуется независимо (не виртуально) с помощью B и C, тогда компилятор может "t разрешить D:: x в B:: x или C:: x, не требуя, чтобы программист явно их устранил. Это означает, что D не может использоваться как A, несмотря на наличие не одного, а двух отношений" is-a ", подразумеваемых цепочкой деривации (т.е. если B" является "A, а D" является "B", то пользователь может ожидать/использовать D, как если бы D "был" A).

Почему эта функция разработана так же, как комитет по стандартизации?

virtual Наследование существует, потому что оно иногда полезно. Он задается B и C, а не D, потому что это интрузивная концепция с точки зрения дизайна B и C, а также имеет последствия для инкапсуляции, компоновки памяти, построения и уничтожения и отправки функций B и C.

Что мы можем сделать, если классы B и C поступают из сторонней библиотеки?

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

EDIT: Мой ответ состоял в том, чтобы указать классы B и C, что они не должны вызывать конструктор всякий раз, когда его производный объект создается, поскольку он будет вызываться D.

Это важный аспект этого, да.

Ответ 3

В дополнение к запросу templatetypedef можно указать, что вы также можете обернуть A в

class AVirt:virtual public A{}; 

и наследует от него другие классы. Вам не нужно явно указывать другие свойства как виртуальные в этом случае

Ответ 4

Вопрос: Почему виртуальное наследование необходимо выполнять в B и C, хотя двусмысленность в D?

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

С множественным наследованием вы не можете этого сделать, потому что вначале нет единого родительского макета. Более того (если вы хотите избежать дублирования) макеты родителей должны пересекаться по атрибутам A. Множественное наследование в С++ скрывает довольно много сложности.

Ответ 5

Поскольку A - это многомножественно унаследованный класс, это те, которые непосредственно вытекают из него, которые должны сделать это виртуальным.

Если у вас есть ситуация, когда B и C выходят из A, и вы хотите как в D, так и вы не можете использовать алмаз, тогда D может происходить только из одного из B и C и иметь экземпляр другого, через который он может пересылать функции.

обходной путь:

class B : public A; // not your class, cannot change
class C : public A; // not your class, cannot change

class D : public B; // your class, implement the functions of B
class D2 : public C; // your class implement the functions of C

class D
{
   D2 d2;
};