Размер объекта C++ с виртуальными методами
у меня есть некоторые вопросы о размере объекта с virtual.
1) виртуальная функция
class A {
public:
int a;
virtual void v();
}
размер класса A составляет 8 байт....одно целое число(4 байта) плюс один виртуальный указатель(4 байта) Все чисто!
class B: public A{
public:
int b;
virtual void w();
}
каков размер класса B? Я тестировал с помощью sizeof B, он печатает 12
означает ли это, что только один vptr есть даже у класса B и класса A есть виртуальная функция? Почему есть только один vptr?
class A {
public:
int a;
virtual void v();
};
class B {
public:
int b;
virtual void w();
};
class C : public A, public B {
public:
int c;
virtual void x();
};
размер C равен 20........
кажется, что в этом случае два vptrs находятся в макете.....Как это происходит? Я думаю, что два vptrs-для класса А и класса Б....так нет vptr для виртуальной функции класса C?
мой вопрос в том, каково правило о количестве vptrs в наследовании?
2) виртуальное наследование
class A {
public:
int a;
virtual void v();
};
class B: virtual public A{ //virtual inheritance
public:
int b;
virtual void w();
};
class C : public A { //non-virtual inheritance
public:
int c;
virtual void x();
};
class D: public B, public C {
public:
int d;
virtual void y();
};
в размер в 8 байт -------------- 4 (int a) + 4 (vptr) = 8
размер B составляет 16 байт -------------- без virtual это должно быть 4 + 4 + 4 = 12. зачем там еще 4 вот байт? Какова планировка класса B ?
размер C составляет 12 байт. -------------- 4 + 4 + 4 = 12. Все чисто!
размер D составляет 32 байта -------------- должно быть 16 (класс B) + 12(класс C) + 4(int d) = 32. Это правда?
class A {
public:
int a;
virtual void v();
};
class B: virtual public A{ //virtual inheritance here
public:
int b;
virtual void w();
};
class C : virtual public A { //virtual inheritance here
public:
int c;
virtual void x();
};
class D: public B, public C {
public:
int d;
virtual void y();
};
sizeof a равно 8
sizeof B является 16
sizeof C равно 16
размер D равен 28 означает ли это 28 = 16 (класс B) + 16(класс C) - 8(класс A) + 4 ( что это? )
мой вопрос в том, почему существует дополнительное пространство при применении виртуального наследования?
каково правило снизу для размера объекта в этом случае?
в чем разница, когда virtual применяется ко всем базовым классам и к части базовых классов?
6 ответов
Это все от реализации. Я использую VC10 Beta2. Ключ, чтобы помочь понять этот материал (реализация виртуальных функций), вам нужно знать о секретном коммутаторе в компиляторе Visual Studio, / d1reportSingleClassLayoutXXX. Я займусь этим через секунду.
основным правилом является то, что vtable должен быть расположен со смещением 0 для любого указателя на объект. Это подразумевает несколько vtables для множественного наследования.
пара вопросов вот, я начну сверху:
означает ли это, что только один vptr есть даже у класса B и класса A есть виртуальная функция? Почему существует только один vptr?
вот как работают виртуальные функции, вы хотите, чтобы базовый класс и производный класс имели один и тот же указатель vtable (указывающий на реализацию в производном классе.
кажется, что в этом случае два vptrs находятся в макете.....Как это происходит? Я думаю, что два vptrs один для класса А, а другой для класса В. таким образом, нет vptr для виртуальной функции класса C?
это макет класса C, как сообщает /d1reportSingleClassLayoutC:
class C size(20):
+---
| +--- (base class A)
0 | | {vfptr}
4 | | a
| +---
| +--- (base class B)
8 | | {vfptr}
12 | | b
| +---
16 | c
+---
вы правы, есть два vtables-по одной для каждого базового класса. Вот как это работает в множественном наследовании; если C * приведен к B*, значение указателя корректируется на 8 байтов. Vtable по-прежнему должен быть со смещением 0 для вызовов виртуальных функций работа.
vtable в приведенном выше макете для класса A рассматривается как vtable класса C (при вызове через C*).
размер B составляет 16 байт -------------- без virtual это должно быть 4 + 4 + 4 = 12. зачем там еще 4 вот байт? Какова планировка класса B ?
это макет класса B в этом примере:
class B size(20):
+---
0 | {vfptr}
4 | {vbptr}
8 | b
+---
+--- (virtual base A)
12 | {vfptr}
16 | a
+---
как вы можете видеть, есть дополнительный указатель для обработки виртуального наследования. Виртуальное наследование сложный.
размер D составляет 32 байта -------------- должно быть 16 (класс B) + 12(класс C) + 4(int d) = 32. Это правда?
нет, 36 байт. То же самое касается виртуального наследования. Макет D в этом примере:
class D size(36):
+---
| +--- (base class B)
0 | | {vfptr}
4 | | {vbptr}
8 | | b
| +---
| +--- (base class C)
| | +--- (base class A)
12 | | | {vfptr}
16 | | | a
| | +---
20 | | c
| +---
24 | d
+---
+--- (virtual base A)
28 | {vfptr}
32 | a
+---
мой вопрос в том, почему существует дополнительное пространство при применении виртуального наследования?
виртуальный указатель базового класса, это сложно. Базовые классы "объединяются" в виртуальном наследовании. Вместо базового класса, встроенного в класс, класс будет иметь указатель на объект базового класса в макете. Если у вас есть два базовых класса, использующих виртуальное наследование (иерархия классов "Алмаз"), они оба будут указывать на один и тот же виртуальный базовый класс в объекте вместо отдельной копии этого базового класса.
каково правило снизу для размера объекта в этом случае?
важный момент: нет никаких правил: компилятор может делай все, что нужно.
и последняя деталь; чтобы сделать все эти диаграммы компоновки классов, которые я компилирую:
cl test.cpp /d1reportSingleClassLayoutXXX
где XXX-это подстрока, соответствующая структурам / классам, которые вы хотите увидеть. Используя это, вы можете самостоятельно изучить влияние различных схем наследования, а также Почему/где добавляется дополнение и т. д.
цитата> мой вопрос в том, каково правило о количестве vptrs в наследовании?
нет правил, каждому поставщику компилятора разрешено реализовать семантику наследования так, как он считает нужным.
класс B: public a {}, size = 12. Это довольно нормально, один vtable для B, который имеет оба виртуальных метода, указатель vtable + 2 * int = 12
класс C: public A, public B {}, size = 20. C может произвольно расширять vtable либо A, либо B. 2 * указатель vtable + 3 * int = 20
виртуальное наследование: вот где вы действительно попали в края недокументированное поведение. Например, в MSVC становятся актуальными параметры компиляции #pragma vtordisp и /vd. Есть некоторая справочная информация в в этой статье. Я изучил это несколько раз и решил, что аббревиатура compile option является репрезентативной для того, что может случиться с моим кодом, если я когда-либо его использую.
хороший способ подумать об этом-понять, что нужно сделать, чтобы справиться с бросками вверх. Я постараюсь ответить на ваши вопросы, показав макет памяти объектов классов, которые вы описываете.
образец кода #2
разметка памяти выглядит следующим образом:
vptr | A::a | B::b
передача указателя на B для ввода A приведет к тому же адресу, с тем же vptr используется. Вот почему здесь нет необходимости в дополнительных vptr.
пример кода #3
vptr | A::a | vptr | B::b | C::c
как вы можете видеть, здесь есть два vptr, как вы догадались. Почему? Потому что это правда, что если мы передаем с C на A, нам не нужно изменять адрес и, таким образом, можем использовать тот же vptr. Но если мы переходим от C к B, мы do нужна эта модификация, и, соответственно, нам нужен vptr в начале результирующего объекта.
таким образом, любой унаследованный класс за первым потребует дополнительного vptr (если этот унаследованный класс не имеет виртуальные методы, в этом случае он не имеет vptr).
пример кода #4 и выше
когда вы производите виртуально, вам нужен новый указатель, называемый базовый указатель, чтобы указать расположение в макете памяти производных классов. Конечно, может быть несколько базовых указателей.
Итак, как выглядит макет памяти? Это зависит от компилятора. В вашем компиляторе это, вероятно, что-то вроде
vptr | base pointer | B::b | vptr | A::a | C::c | vptr | A::a \-----------------------------------------^
но другие компиляторы может включать базовые указатели в виртуальную таблицу (используя смещения - это заслуживает другого вопроса).
вам нужен базовый указатель, потому что, когда вы производите виртуальным способом, производный класс появится только один раз в макете памяти (он может появиться дополнительно, если он также производный нормально, как в вашем примере), поэтому все его дочерние элементы должны указывать на одно и то же местоположение.
EDIT: clarification-все действительно зависит от компилятора, макета памяти I showed может быть разным в разных компиляторах.
все это полностью определено реализацией, которую вы понимаете. Ты не можешь на это рассчитывать. Нет никакого "правила".
в Примере наследования вот как может выглядеть виртуальная таблица для классов A и B:
class A
+-----------------+
| pointer to A::v |
+-----------------+
class B
+-----------------+
| pointer to A::v |
+-----------------+
| pointer to B::w |
+-----------------+
Как вы можете видеть, если у вас есть указатель на виртуальную таблицу класса B,она также совершенно действительна как виртуальная таблица класса A.
в вашем примере класса C, если вы думаете об этом, нет способа сделать виртуальную таблицу, которая является и допустимо как таблица для класса C, класса A и класса B. поэтому компилятор делает два. Одна виртуальная таблица действительна для классов A и C (скорее всего), а другая действительна для классов A и B.
Это, очевидно, зависит от реализации компилятора. Во всяком случае, я думаю, что могу суммировать следующие правила из реализации, заданной классической бумагой, связанной ниже, и которая дает количество байтов, которое вы получаете в своих примерах (за исключением класса D, который будет 36 байтов, а не 32!!!):
размер объекта класса T:
- размер его полей плюс сумма размера каждого объекта, от которого T наследует плюс 4 байта для каждого объекта из которого T фактически наследует плюс 4 байта, только если T нуждается в другой v-таблице
- обратите внимание: если класс K фактически наследуется несколько раз (на любом уровне), вы должны добавить размер K только один раз
поэтому мы должны ответить на другой вопрос: когда классу нужна другая v-таблица?
- класс, который не наследует от других классов должен по таблице, только если он имеет один или несколько виртуальных методов
- в противном случае класс должен другая v-таблица, только если ни один из классов, от которых она фактически не наследуется, не имеет V-таблицы
конец правил (которые, я думаю, можно применить, чтобы соответствовать тому, что объяснил Терри Махаффи в своем ответе):)
в любом случае мое предложение-прочитать следующую статью Бьярне Страуструпа (создателя C++), которая объясняет именно эти вещи: сколько виртуальных таблиц необходимо с виртуальным или не виртуальным наследованием... и почему!
Это действительно хорошее чтение: http://www.hpc.unimelb.edu.au/nec/g1af05e/chap5.html
Я не уверен, но думаю, что это из-за указателя на таблица виртуальных методов