#c #oop #polymorphism #virtual-machine
Вопрос:
#include "stdafx.h"
#include <iostream>
using namespace std;
class ClassA
{
protected:
int width, height;
public:
void set_values(int x, int y)
{
width = x;
height = y;
}
};
class ClassB : virtual public ClassA
{
//12(int int vptr)
};
class ClassC : virtual public ClassA
{
//12(int int vptr)
};
class ClassD : public ClassB, public ClassC
{
};
int main()
{
ClassA A;
ClassB B;
ClassC C;
ClassD D;
cout << "size = " << sizeof(A) << endl;
cout << "size = " << sizeof(B) << endl;
cout << "size = " << sizeof(C) << endl;
cout << "size = " << sizeof(D) << endl;
return 0;
}
результат, который я получил, таков:
size of ClassA = 8
size of ClassB = 12
size of ClassC = 12
size of ClassD = 16
В приведенном выше коде, почему вывод равен 16 для ClassD. пожалуйста, объясните мне ясно, как работает это виртуальное наследование.
Комментарии:
1. Это зависит от реализации. Почему вы все равно хотите полагаться на размер, чтобы быть чем-то конкретным? Если вам нужен размер, просто используйте
sizeof
, и он вернет соответствующий размер, какой бы он ни был. Знать это в теоретических целях-это прекрасно, но это не имеет абсолютно никакого практического применения.2. int int 2 * vptr?
3. просто чтобы понять, как происходит это виртуальное наследование. по моему мнению, размер ClassD должен составлять 24 байта, т. е.(int int vptr) (int int vptr) = 24 байта . так что я запутался, как это работает, так что спрашивайте..
4. @Jon: хотя здесь нет виртуальных функций, поэтому не должно быть необходимости в каких-либо vptrs, AFAICS.
5. @OliCharlesworth Виртуальная таблица необходима для того, чтобы найти
ClassA
подобъект.
Ответ №1:
Виртуальное наследование означает, что виртуальные базовые классы существуют только один раз, а не несколько. Вот почему 8 байт от ClassA
находятся только в ClassD
одном разе. Само виртуальное наследование требует определенных накладных расходов, и, следовательно, вы получаете дополнительный указатель. Точная реализация и, следовательно, точные накладные расходы не указаны стандартом C и могут варьироваться в зависимости от создаваемой иерархии.
Комментарии:
1. » 12 байт из » Вы имеете в виду 8 байт?
Ответ №2:
Когда ClassD наследует ClassB и ClassC ,будет две vptr (одна из B и одна из C). Именно этот случай описан в статье Скотта Мейерса «Более эффективный C «, Статья 24 (Стоимость различных функций языка).
Ответ №3:
Реализации виртуальных базовых классов
Виртуальные базовые классы в точности похожи на виртуальные функции: их адрес (или относительное смещение адреса) неизвестен во время компиляции:
void f(ClassB *pb) {
ClassA *pa = pb;
}
Здесь компилятор должен вычислить смещение ClassA
базового подобъекта от ClassB
подобъекта (или в основном производного объекта). Некоторые компиляторы просто имеют указатель на него внутри ; другие используют таблицу v, как и для виртуальных функций. ClassB
В обоих случаях накладные ClassB
расходы составляют один указатель.
Это ClassC
похоже, но vptr будет указывать на ClassC
ClassB
виртуальную таблицу, а не на виртуальную таблицу.
Таким образом ClassD
, объект будет содержать (это не упорядоченный список):
- один
ClassA
подобъект - тема
ClassB
- тема
ClassC
Так ClassD
же есть два унаследованных vptr: от ClassB
и ClassC
. В ClassD
объекте оба vptr будут указывать на некоторую ClassD
виртуальную таблицу, но ClassD
одну и ту же виртуальную таблицу:
ClassB
объект указывает на таблицу v ClassB-в-ClassD, которая указывает относительное положениеClassA
базы отClassB
базыClassC
объект указывает на таблицу vtable ClassC-в-ClassD, которая указывает относительное положениеClassA
базы отClassC
базы
Возможная оптимизация
Я предполагаю, что ваш вопрос таков: нужны ли нам два разных vptr?
Технически иногда возможно оптимизировать размер классов путем наложения подобъектов базового класса. Это один из тех случаев, когда это технически возможно:
Наложение (или объединение) означает , что оба ClassB
и ClassC
будут использовать один и тот же vptr: с учетом d
экземпляра ClassD
: amp;d.ClassB::vptr == amp;d.ClassC::vptr
so d.ClassB::vptr == d.ClassC::vptr
, но d.ClassB::vptr == amp;ClassC_in_ClassD_vtable
и d.ClassC::vptr == amp;ClassC_in_ClassD_vtable
, so ClassB_in_ClassD_vtable
должны быть объединены ClassC_in_ClassD_vtable
. В данном конкретном случае оба ClassB_in_ClassD_vtable
и ClassC_in_ClassD_vtable
используются только для описания смещения ClassA
подобъекта; если ClassB
и ClassC
подобъекты объединены ClassD
, то эти смещения также объединены, следовательно, возможна унификация таблиц vt.
Обратите внимание, что это возможно только здесь, поскольку существует совершенное сходство. Если ClassB
бы и ClassC
были изменены, чтобы добавить хотя бы одну виртуальную функцию в каждую, например, эти виртуальные функции не эквивалентны (следовательно, не могут быть объединены), объединение vtable было бы невозможно.
Вывод
Такая оптимизация возможна только в очень простых случаях, подобных этому. Эти случаи не типичны для программирования на C : люди обычно используют виртуальные базовые классы в сочетании с виртуальными функциями. Оптимизация пустого базового класса полезна, поскольку многие идиомы C используют базовые классы без элементов данных или виртуальных функций. OTOH, крошечная (один vptr) оптимизация пространства для специального использования виртуальных базовых классов не кажется полезной для программ реального мира.