Библиография
[1] http://phpcompiler.org/articles/virtualinheritance.html
[2] http://www.openrce.org/articles/files/jangrayhood.pdf
Вопрос, что выведет программа №1:
#include <stdio.h>class A{};class B1: virtual public A{};class B2: virtual public A{};class B3: virtual public A{};class B4: virtual public A{};class FROM_B1_B2 : public B1, public B2{};class FROM_B1_B2_B3_B4 : public B1, public B2, public B3, public B4{};#define PRINTSIZE(class_name) printf("sizeof class '" #class_name "' is equal to %u bytes\n", sizeof(class_name))int main(int argc, const char** argv){ PRINTSIZE(FROM_B1_B2); PRINTSIZE(FROM_B1_B2_B3_B4); return 0;}
Вопрос, что выведет программа №2:
#include <stdio.h>#define VIRTUAL virtual// #define VIRTUALclass Top {public: // ~Top() { printf("~Top()\n"); } Top() { printf("Top()\n"); } Top(int) { printf("Top(int)\n"); } };class Left : VIRTUAL public Top {public: // ~Left() { printf("~Left()\n"); } Left() { printf("Left()\n"); } Left(int a) : Top(a) { printf("Left(int)\n"); } };class Right : VIRTUAL public Top {public: // ~Right() { printf("~Right()\n"); } Right() { printf("Right()\n"); } Right(int a) : Top(a) { printf("Right(int)\n"); } };class Bottom : public Left, public Right {public: // ~Bottom() { printf("~Bottom()\n"); } Bottom() { printf("Bottom()\n"); } Bottom(int a) : Left(a), Right(a) { printf("Bottom(int)\n"); }};int main(int argc, const char** argv) { Bottom b(123); return 0;}
Приблизительный ответ:
В статье http://phpcompiler.org/articles/virtualinheritance.html трогается вопрос виртуального наследования, в ней и содержатся ответы на эти вопросы.
Вопрос №1 implementation-specific, Вопрос №2 муть.
Но всё таки, если начать копать то можно докопаться до:
1. Про оператор sizeof
"ANSI ISO IEC 14882 C++ 2003
5.3.3
The sizeof operator yields the number of bytes in the object representation of its operand. The operand
is either an expression, which is not evaluated, or a parenthesized type-id...
When applied to a class, the result is the number of bytes in an object of that class including any padding required for
placing objects of that type in an array...."
В прямом виде не сказано хранятся ли скрытые поля объекта, сказано лишь про padding.
Будем считать такие скрытые поля, тоже включаются в размер объекта.
2. По поводу наследования не-виртуального, можно поиграться с таким участком кода
#include <stdio.h>#define PRINTADRESS(lvalue) printf("adress of '" #lvalue " is: %08X\n", (void*)&(lvalue))class Simple{public: int base1; int base2;};class SimpleBased : public Simple {public: int der1; int der2;};int main(int argc, const char** argv){ SimpleBased simple_based; PRINTADRESS(simple_based.der1); PRINTADRESS(simple_based.der2); PRINTADRESS(simple_based.base1); PRINTADRESS(simple_based.base2); PRINTADRESS(simple_based); PRINTADRESS((Simple&)(simple_based)); //PRINTSIZE(FROM_B1_B2); //PRINTSIZE(FROM_B1_B2_B3_B4); return 0;}
Simple, SimpleBased и поле Simple::base1 в реализации C++ компилятора Visual Studio 2012 мапяться на один и тот же адрес.
В gcc 5.5 например так же.
Суть приведения типов в C++ сдвигания окна по памяти так, чтобы данные базового типа на который мы смотрим по memory layout были бы идентичны тому, на что мы смотрели бы если создали объекта базового класса, без производного типа.
При использовании виртуального наследования, к примеру ромбовидного, происходит создание общего данного для двух путей наследования от базового класса. Создать такое вот окно, чтобы смотреть "красиво" на данное лежащее аккуратно в памяти уже не выйдет, поэтому используется приблизительно следующий трюк.
Кстати, именно ключевое слово virtual следует использовать для ветвей наследования которые идут от общего предка для разных путей наследования. В примере выше это Right и Left.
Для каждого класса который обращается к расшаренным данным, в примере это классы 'B*' создаются поля типа vptr.B1, которые указывают на таблицу vtable.
Эта таблица состоит из:
1. virtual base offset -- кол-во байт которое нужно добавить к адресу поля vptr.B1 чтобы получить адрес расшаренные переменные из class A
2. top -- смещение поля vptr.B1 от начала структуры. Может потребоваться для сложных сценариев разобранных в http://phpcompiler.org/articles/virtualinheritance.html, но которые здесь я трогать не буду.
3. Значение typeinfo
4. Здесь, если имеют место быть должны идти указатели на реальные вызываемые функции для таблицы виртуальных функций.
Про 1-4 это мои фантазии. В http://en.wikipedia.org/wiki/Virtual_method_table не указано ничего про 1-4.
В оригинальной статье про компилятор gcc сказано про 1-3.
На примере сборки программы toolchain-ом для Visual Studio 2012 для 32-ух разрядной архитектуры программа #1 выводит следующее
sizeof class 'FROM_B1_B2' is equal to 8 bytes
sizeof class 'FROM_B1_B2_B3_B4' is equal to 16 bytes
Про конструктора виртуального базового класса.
Компилятор должен гарантировать, что все конструкторы виртуальных базовых классов отработали один раз. Если не вызывать конструктор базового класса виртуального явно, он будет дёрнут неявно. Дёргать его надо независимо от того, как далеко мы находимся в графе наследования классов. И это конечно может удивлять, т.к. немного необычно
Программа #2 выводит следующее
Top()
Left(int)
Right(int)
Bottom(int)