размер производного класса в виртуальном наследовании

#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) оптимизация пространства для специального использования виртуальных базовых классов не кажется полезной для программ реального мира.