Почему виртуальное наследование должно выполняться в B и C, даже если неопределенность в D? Это было бы более интуитивно понятно, если бы оно было на D.
В вашем примере B и C используют virtual
специально, чтобы попросить компилятор убедиться, что задействована только одна копия A. Если они этого не сделали, они фактически говорят: «Мне нужен мой собственный базовый класс, я не собираюсь делиться им с любым другим производным объектом». Это может иметь решающее значение.
Пример нежелания делиться виртуальным базовым классом
Если A был чем-то вроде контейнера, B был получен из него и хранил некоторый конкретный тип объекта - скажем, "Bat", в то время как C хранит "Cat". Если D ожидает, что B и C будут независимо предоставлять информацию о популяции летучих мышей и кошек, они будут очень удивлены, если операция C сделала что-то с / с летучими мышами, или операция B сделала что-то с / с кошками.
Пример использования виртуального базового класса
Скажем, D должен предоставить доступ к некоторым функциям или членам данных, которые находятся в A, скажем, "A :: x" ... если A наследуется независимо (не виртуально) B и C, то компилятор может Разрешить 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, от которого он напрямую наследует (если ему нужны видимые отношения "is A"). Это может быть практичным, если вызывающий код знает, что он имеет дело с D (даже если с помощью шаблонов), но операции над объектом через указатели на базовые классы не будут знать об управлении, которое D пытается выполнить, и все это может быть очень сложно получить права. Но это все равно, что сказать «что, если мне нужен вектор, а у меня есть только список», «пила, а не отвертка» ... ну, максимально используйте это или получите то, что вам действительно нужно.
РЕДАКТИРОВАТЬ: Мой ответ состоял в том, чтобы указать классам B и C, что они не должны вызывать конструктор A всякий раз, когда создается его производный объект, так как он будет вызываться D.
Это важный аспект этого, да.