2012-03-16 2 views
6

Учитывая код:Множественное наследование: размер класса для виртуальных указателей?

class A{}; 

class B : public virtual A{}; 

class C : public virtual A{}; 

class D : public B,public C{}; 

int main(){ 
cout<<"sizeof(D)"<<sizeof(D); 
return 0; 
} 

Выход: SizeOf (D) 8

Каждый класс содержит свой собственный виртуальный указатель только не любой из его базового класса, Итак, почему размер класса (D) составляет 8?

+0

Вам нужно сказать, какой компилятор и архитектура вы используете. –

+0

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

ответ

3

Это зависит от реализации компилятора. Мой компилятор Визуальная STDIO C++ 2005.

код так:

int main(){ 
    cout<<"sizeof(B):"<<sizeof(B) << endl; 
    cout<<"sizeof(C):"<<sizeof(C) << endl; 
    cout<<"sizeof(D):"<<sizeof(D) << endl; 
    return 0; 
} 

Он будет выводить

sizeof(B):4 
sizeof(C):4 
sizeof(D):8 

класс B имеет только один виртуальный указатель. Итак, sizeof(B)=4. И класс C также.

Но D множественное наследование class B и class C. Компиляция не объединяет две виртуальные таблицы. Так что class D имеет две виртуальные точки указателя на каждую виртуальную таблицу.

Если D только наследование одного класса, а не виртуального наследования. Он объединит их виртуальную таблицу.

+1

Я согласен! Мы просто догадываемся, потому что это деталь реализации компилятора (с его версией/архитектурой и различиями в платформе), но это правдоподобно. –

2

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

Чтобы проверить это, вы можете объявить указатель к B и указатель на C и отбрасывать адрес D указателю базового класса. Дампируйте эти значения, и вы увидите, что они разные!

EDIT
Испытание с использованием Visual C++ 10.0, 32 бит.

class Base 
{ 
}; 

class Derived1 : public virtual Base 
{ 
}; 

class Derived2 : public virtual Base 
{ 
}; 

class Derived3 : public virtual Base 
{ 
}; 

class ReallyDerived1 : public Derived1, public Derived2, public Derived3 
{ 
}; 

class ReallyDerived2 : public Derived1, public Derived2 
{ 
}; 

class ReallyDerived3 : public Derived2 
{ 
}; 

void _tmain(int argc, _TCHAR* argv[]) 
{ 
std::cout << "Base: " << sizeof(Base) << std::endl; 
std::cout << "Derived1: " << sizeof(Derived1) << std::endl; 
std::cout << "ReallyDerived1: " << sizeof(ReallyDerived1) << std::endl; 
std::cout << "ReallyDerived2: " << sizeof(ReallyDerived2) << std::endl; 
std::cout << "ReallyDerived3: " << sizeof(ReallyDerived3) << std::endl; 
} 

выход, думаю, это не удивительно:

  • Основание: 1 байт (OK, это сюрприз, по крайней мере, для меня).
  • Derived1: 4 байта
  • ReallyDerived1: 12 байт (4 байта на базовый класс из-за множественного наследования)
  • ReallyDerived2: 8 байтов (как это угадали)
  • ReallyDerived3: 4 байта (только один базовый класс с виртуальной наследование в пути, но это не виртуально).

При добавлении виртуального метода к базе вы получаете по 4 байта для каждого класса. Поэтому, возможно, дополнительные байты не являются указателями vtable, но указатели базового класса, используемые в множественном наследовании, это поведение не меняет удаление виртуального наследования (но если не виртуальный размер не меняет добавление дополнительных баз).

+1

В этом случае я был бы удивлен, если бы были указатели на 'vtables'. Это потребует не менее 3. За исключением того, что без каких-либо виртуальных функций, это не требует никаких. (И нет способа конвертировать 'A *' в 'B *'.) –

+0

Я ** угадаю ** (если кто-то знает, пожалуйста, исправьте меня!) Компилятор создает указатель на базовый класс даже без виртуальных методов, потому что множественное наследование (потому что это необходимо для downcasts). Должно быть только два указателя на базовый класс vtable, один для базового класса и ничего для ** C ** сам по себе, потому что нет виртуальных методов.
Я имел в виду: объявить указатель B * pB и другой указатель C * pC. Затем перетащите указатель на D и назначьте его pB и pC. :) –

+0

Я использую компилятор g ++ – Luv

1

Во-первых: без виртуальных функций, возможно, что нет классов vptr. 8 байтов, которые вы видите, являются артефактом того, как реализовано виртуальное наследование.

Для нескольких классов в иерархии часто бывает одинаково vptr. Для этого необходимо, чтобы их смещение в конечном классе было одинаковым, а для списка записей vtable в базовом классе была исходная последовательность списка записей vtable в производном классе .

Оба условия выполнены практически во всех реализациях для одиночного наследования.Независимо от того, насколько глубоким наследование, обычно будет только один vptr, используемый всеми классами.

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

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

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

Если вы пытаетесь проанализировать то, что происходит, я бы предложить добавить новую виртуальную функцию в каждом классе, и добавление указателя размера интегрального типа, который вы начинаете с другого известного значения для каждого класса . (Используйте конструкторы для установки значения.) Затем создайте экземпляр класса , возьмите его адрес и затем выведите адрес для каждого базового класса . А затем сбросьте класс: известные фиксированные значения помогут в идентифицировать, где лежат разные элементы. Что-то вроде:

struct VB 
{ 
    int vb; 
    VB() : vb(1) {} 
    virtual ~VB() {} 
    virtual void fvb() {} 
}; 

struct Left : virtual VB 
{ 
    int left; 
    Left() : left(2) {} 
    virtual ~Left() {} 
    virtual void fvb() {} 
    virtual void fleft() {} 
}; 

struct Right : virtual VB 
{ 
    int right; 
    Right() : right(3) {} 
    virtual ~Right() {} 
    virtual void fvb() {} 
    virtual void fright() {} 
}; 

struct Derived : Left, Right 
{ 
    int derived; 
    Derived() : derived(5) {} 
    virtual ~Derived() {} 
    virtual void fvb() {} 
    virtual void fleft() {} 
    virtual void fright() {} 
    virtual void fderived() {} 
}; 

Вы можете добавить Derived2, который вытекает из Derived и увидеть , что происходит с относительными адресами между, например, Left и VB в зависимости от того, имеет ли объект тип Derived или Derived2.

+0

Проверьте с более чем двумя базовыми классами, я думаю, что это не из-за виртуальных методов, а потому, что указатель на базовый класс «vtable» (присутствует, даже если его не существует) –

+0

Если виртуальных функций нет, 'vtable' не является но какие-то указатели на виртуальный базовый класс (поскольку они необходимы для преобразования 'Derived *' в 'VB *'). –

0

Вы делаете слишком много допущений. Это сильно зависит от ABI, поэтому вы должны изучить документацию для своей платформы (я предполагаю, что вы работаете на 32-битной платформе).

Первое, что в вашем примере нет виртуальных функций, а это значит, что ни один из типов на самом деле не содержит указатель на виртуальную таблицу. Итак, откуда взялись эти 2 указателя? (Я предполагаю, что вы на 32-битной архитектуре). Ну, виртуальное наследование - это ответ. Когда вы наследуете фактически, относительное расположение виртуальной базы (A) относительно дополнительных элементов производного типа (B, C) будет изменяться вдоль цепочки наследования. В случае объекта B или C компилятор может класть типы как [A, B '] и [A, C'] (где X '- дополнительные поля X, не присутствующие в A).

Теперь виртуальное наследование означает, что в случае D будет только один подобъект A, поэтому компилятор может компоновать тип D как [A, B ', C', D] или [A, C ', B ', D] (или любая другая комбинация, A может быть в конце объекта и т. Д., Это определено в ABI). Итак, что это подразумевает, это подразумевает, что функции-члены из B и C не могут предполагать, где может быть подобъект A (в случае не виртуального наследования, относительное местоположение известно), потому что полный тип может фактически быть некоторым другой тип вниз по цепочке.

Решение проблемы состоит в том, что как B, так и C обычно содержат дополнительный указатель указатель на базу, аналогичный, но не эквивалентный виртуальному указателю. Точно так же, как vptr используется для динамической отправки функции, этот дополнительный указатель используется для динамического поиска базы.

Если вы заинтересованы во всех этих деталях, я рекомендую вам прочитать Itanium ABI, который широко используется не только в Itanium, но и в других архитектурах Intel 64 (и модифицированной версии в 32 архитектурах) различными компиляторами.

Смежные вопросы