$4.11/2 states -
An rvalue of type “pointer to member of
B
of type cvT
,” whereB
is a class type, can be converted to an rvalue of type “pointer to member ofD
of type cvT
,” whereD
is a derived class (clause 10) ofB
. IfB
is an inaccessible (clause 11), ambiguous (10.2) or virtual (10.1) base class ofD
, a program that necessitates this conversion is ill-formed.
My question is why we have the restriction of B
not being a virtual base class of D
?
Рассмотрим ситуацию с невиртуальным базовым классом:
class A { int a; }
class B : public A { int b; }
class C : public A { int c; }
class D : public B, public C { int d; }
Вот возможная структура памяти:
+-------------+
| A: int a; |
+-------------+
| B: int b; |
+-------------+
| A: int a; |
+-------------+
| C: int c; |
+-------------+
| D: int d; |
+-------------+
D
заканчивается двумя подобъектами A
, потому что он наследуется от B
и C
, и каждый из них имеет подобъект A
.
Указатели на переменные-члены обычно реализуются как целочисленное смещение от начала объекта. В этом случае целочисленное смещение для int a
в объекте A
равно нулю. Таким образом, «указатель на int a
типа A
» может быть просто целочисленным смещением нуля.
Чтобы преобразовать "указатель на int a
типа A
" в »указатель на int a
типа B
, "вам просто нужно целочисленное смещение для подобъекта A
, расположенного в B
(первый подобъект A
).
Чтобы преобразовать указатель на int a
типа A
«в» указатель на int a
типа C
, "вам просто нужно целочисленное смещение для подобъекта A
, расположенного в C
(второй подобъект A
).
Поскольку компилятор знает, где B
и C
относительно A
, компилятор имеет достаточно информации о том, как выполнить понижающее приведение от A
] на B
или C
.
Теперь рассмотрим ситуацию с виртуальным базовым классом:
struct A { int a; }
struct B : virtual public A { int b; }
struct C : virtual public A { int c; }
struct D : public B, public C { int d; }
Возможная структура памяти:
+-------------+
| B: ptr to A | ---+
| int b; | |
+-------------+ |
| C: ptr to A | ---+
| int c; | |
+-------------+ |
| D: int d; | |
+-------------+ |
| A: int a; | <--+
+-------------+
Виртуальные базовые классы обычно реализуются с помощью B
и C
(которые виртуально производят from A
) содержат указатель на единственный субъект A
. Указатели на подобъект A
необходимы, потому что расположение A
относительно B
и C
не является постоянным.
Если бы все, что у нас было, было «указатель на int a
типа A
], мы не смогли бы преобразовать его в» указатель на int a
типа B
", поскольку расположение подобъектов B
и C
может изменяться относительно A
. A
не имеет обратных указателей на B
или C
, поэтому у нас просто недостаточно информации для работы понижающего преобразования.
При невиртуальном наследовании члены базового класса и производного класса могут быть расположены в памяти непрерывно, причем сначала базовый класс, так что каждый член базового класса находится в одном месте относительно адреса объекта. является ли объект B
или D
. Это упрощает преобразование указателя на член B
в указатель на член D
; оба могут быть представлены как смещение от адреса объекта.
При виртуальном наследовании к членам базового класса необходимо обращаться через указатель (или эквивалент) в производном объекте, указывающий, где расположен базовый класс. Это потребует добавления дополнительной информации к представлению указателя на член, чтобы указать, что это косвенное обращение необходимо, и потребует проверки во время выполнения при использовании любого указателя на член.
Общий принцип, лежащий в основе большей части C ++, - по возможности избегать накладных расходов во время выполнения. В этом случае выбор был между проверкой во время выполнения довольно распространенной операции и запретом довольно непонятного преобразования, и похоже, что этот принцип был применен здесь.
Действительно интересный вопрос. Узнал что-то новое сегодня. Вот что я смог найти по теме: Преобразование указателей функций-членов из производного класса в виртуальный базовый класс не работает