2010-08-17 4 views
7

$ 4.11/2 состояния -Указатель на вопрос члена

Rvalue типа «указатель на член из B типа сортаT», где B является тип класса , могут быть преобразованы в Rvalue из типа «указатель на член D типа сортаT», где D является производным классом (пункт 10) B. Если B является недоступными (пункт 11), неоднозначное (10.2) или виртуальный (10.1) базовый класс D, программа, которая делает необходимым этот преобразование плохо сформирован.

Мой вопрос, почему у нас есть ограничение B не является виртуальным базовым классом D?

ответ

5

Рассмотрит ситуацию с участием невиртуального базового класса:

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 (который фактически выводит из A) содержат указатель на один подзаголовок A. Указатели на подобъект A необходимы, поскольку местоположение A относительно B и C не является постоянным.

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

+0

+1 для образцов ASCII – Bill

1

С не виртуальным наследованием члены базового класса и производного класса могут быть расположены последовательно в памяти с базовым классом сначала, так что каждый элемент базового класса находится в одном и том же месте относительно объекта укажите, является ли объект B или D. Это упрощает преобразование указателя-к-члену-B в указатель-к-члену-D; оба могут быть представлены как смещение от адреса объекта.

С виртуальным наследованием к элементам базового класса должен быть доступен указатель (или эквивалент) в производном объекте, указывающий, где находится базовый класс. Это потребует добавления дополнительной информации в представление «указатель-член», чтобы указать, что это косвенное действие необходимо, и потребовало бы проверки времени выполнения при использовании любого указателя-указателя.

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