У меня есть несколько вопросов о размере объекта с виртуальным.
1) виртуальная функция
class A {
public:
int a;
virtual void v();
}
Размер класса A составляет 8 байт …. одно целое число (4 байта) плюс один виртуальный указатель (4 байта)
Ясно!
class B: public A{
public:
int b;
virtual void w();
}
Каков размер класса B? Я проверил, используя размер B, он печатает
12
Означает ли это, что только один vptr есть, даже у класса B и класса A есть виртуальная функция? Почему там только один вптр?
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 один для класса A, а другой для класса B …. так что нет 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();
};
Размер A составляет 8 байтов ————— 4 (int a) + 4 (vptr) = 8
Размер B составляет 16 байт ————— Без виртуального должно быть 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();
};
Размер А составляет 8
размер B 16
Размер С составляет 16
размер D равен 28 Означает ли это 28 = 16 (класс B) + 16 (класс C) — 8 (класс A) + 4 (что это?)
У меня вопрос: почему при применении виртуального наследования появляется дополнительное пространство?
Какое нижнее правило для размера объекта в этом случае?
Какая разница, когда виртуальный применяется на всех базовых классах и на части базовых классов?
Это все реализация определена. Я использую VC10 Beta2. Ключ к пониманию этой вещи (реализация виртуальных функций), вам нужно знать о секретном переключателе в компиляторе Visual Studio, / d1reportSingleClassLayoutXXX. Я вернусь к этому через секунду.
Основное правило — vtable должен быть расположен со смещением 0 для любого указателя на объект. Это подразумевает несколько vtables для множественного наследования.
Пара вопросов здесь, я начну сверху:
Означает ли это, что только один vptr есть, даже у класса B и класса A есть виртуальная функция? Почему там только один вптр?
Вот как работают виртуальные функции, вы хотите, чтобы базовый класс и производный класс совместно использовали один и тот же указатель vtable (указывающий на реализацию в производном классе.
Похоже, что в этом случае два vptrs находятся в макете ….. Как это происходит? Я думаю, что два vptrs один для класса A, а другой для класса B …. так что нет vptr для виртуальной функции класса C?
Это макет класса C, как сообщает / d1reportSingleClassLayoutC:
class C size(20):
+---
| +--- (base class A)
0 | | {vfptr}
4 | | a
| +---
| +--- (base class B)
8 | | {vfptr}
12 | | b
| +---
16 | c
+---
Вы правы, есть две таблицы, по одной для каждого базового класса. Вот как это работает в множественном наследовании; если C * приведен к B *, значение указателя корректируется на 8 байтов. Для работы вызовов виртуальных функций виртуальная таблица все еще должна иметь смещение 0.
Vtable в приведенном выше макете для класса A рассматривается как vtable класса C (при вызове через C *).
Размер B составляет 16 байт ————— Без виртуального должно быть 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. Это довольно нормально, одна таблица v для B, в которой есть оба виртуальных метода, указатель таблицы vtable + 2 * int = 12
класс C: общедоступный A, общедоступный B {}, размер = 20. C может произвольно расширять vtable таблицы A или B. 2 * указатель vtable + 3 * int = 20
Виртуальное наследование: это то, где вы действительно сталкиваетесь с недокументированным поведением. Например, в MSVC параметры компиляции #pragma vtordisp и / vd становятся актуальными. Есть некоторая справочная информация в Эта статья. Я изучил это несколько раз и решил, что аббревиатура опции компиляции является репрезентативной для того, что может случиться с моим кодом, если я когда-либо его использовал.
Хороший способ подумать об этом — понять, что нужно сделать, чтобы справиться с повышениями. Я постараюсь ответить на ваши вопросы, показывая расположение в памяти объектов классов, которые вы описываете.
Расположение памяти выглядит следующим образом:
вптр | A :: a | B :: б
Повышение указателя на B для типа A приведет к тому же адресу, с тем же VTPR используется. Вот почему здесь нет необходимости в дополнительном vptr.
вптр | A :: a | вптр | B :: b | C :: гр
Как видите, здесь два vptr, как вы и догадались. Зачем? Потому что это правда, что если мы выполняем передачу с C на A, нам не нужно изменять адрес, и поэтому мы можем использовать тот же vptr. Но если мы сбрасываем с C на B, мы делать нужна эта модификация, и соответственно нам нужен vptr в начале результирующего объекта.
Таким образом, любой унаследованный класс, кроме первого, потребует дополнительного vptr (если только у этого унаследованного класса нет виртуальных методов, в этом случае у него нет vptr).
Когда вы выводите виртуально, вам нужен новый указатель, называемый базовый указатель, указать расположение в памяти макета производных классов. Конечно, может быть несколько базовых указателей.
Так как же выглядит макет памяти? Это зависит от компилятора. В вашем компиляторе это, вероятно, что-то вроде
вптр | базовый указатель | B :: b | вптр | A :: a | C :: c | вптр | A :: \ ----------------------------------------- ^
Но другие компиляторы могут включать базовые указатели в виртуальную таблицу (используя смещения — это заслуживает другого вопроса).
Вам нужен базовый указатель, потому что когда вы производите виртуальным способом, производный класс появится только один раз в макете памяти (он может появиться еще раз, если он также выведен нормально, как в вашем примере), поэтому все его дочерние элементы должны указывать на точно такое же место.
РЕДАКТИРОВАТЬ: разъяснение — все это действительно зависит от компилятора, расположение памяти, которое я показал, может быть различным в разных компиляторах.
Все это полностью зависит от реализации, которую вы понимаете. Вы не можете рассчитывать ни на что из этого. Здесь нет «правила».
В примере наследования, вот как может выглядеть виртуальная таблица для классов 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 !!!) :
Размер объекта класса Т:
Итак, мы должны ответить на другой вопрос: когда классу нужен ДРУГОЙ v-стол?
Конец правил (которые, я думаю, можно применить, чтобы соответствовать тому, что Терри Махаффи объяснил в своем ответе) 🙂
В любом случае, я предлагаю прочитать следующую статью Бьярна Страуструпа (создателя C ++), которая объясняет именно эти вещи: сколько виртуальных таблиц необходимо для виртуального или не виртуального наследования … и почему!
Это действительно хорошее чтение:
http://www.hpc.unimelb.edu.au/nec/g1af05e/chap5.html
Я не уверен, но я думаю, что это из-за указателя на Таблица виртуальных методов