Схема памяти множественного наследования C++ с "пустыми классами"
Я знаю, что расположение памяти множественного наследования не определено, поэтому я не должен на это полагаться. Однако могу ли я положиться на это в особом случае. То есть класс имеет только один "настоящий" супер класс. Все остальные являются "пустыми классами", т. Е. Классами, которые не имеют ни полей, ни виртуальных методов (т.е. они имеют только не виртуальные методы). В этом случае эти дополнительные классы не должны ничего добавлять к макету памяти класса. (Более кратко, в формулировке C++11 класс имеет стандартную компоновку)
Могу ли я сделать вывод, что у всех суперклассов не будет смещения? Например:
#include <iostream>
class X{
int a;
int b;
};
class I{};
class J{};
class Y : public I, public X, public J{};
int main(){
Y* y = new Y();
X* x = y;
I* i = y;
J* j = y;
std::cout << sizeof(Y) << std::endl
<< y << std::endl
<< x << std::endl
<< i << std::endl
<< j << std::endl;
}
Вот, Y
это класс с X
быть единственным реальным базовым классом. Вывод программы (при компиляции на Linux с g++4.6) выглядит следующим образом:
8
0x233f010
0x233f010
0x233f010
0x233f010
Как я сделал вывод, настройки указателя нет. Но специфична ли эта реализация или можно на нее положиться? Т.е. если я получу объект типа I
(и я знаю, что существуют только эти классы), могу ли я использовать reinterpret_cast
бросить его X
?
Я надеюсь, что на это я мог бы положиться, потому что в спецификации сказано, что размер объекта должен быть хотя бы байтом. Поэтому компилятор не может выбрать другой макет. Если бы это было макет I
а также J
за членами X
тогда их размер будет нулевым (потому что у них нет членов). Поэтому единственный разумный выбор - выровнять все суперклассы без смещения.
Я прав или я играю с огнем, если я использую reinterpret_cast из I
в X
Вот?
1 ответ
В C++11 компилятор должен использовать Оптимизацию пустого базового класса для стандартных типов макетов. см. /questions/28695592/yavlyaetsya-li-optimizatsiya-pustogo-bazovogo-klassa-teper-obyazatelnoj-optimizatsiej-po-krajnej-mere-dlya-klassov-standartnoj-komponovki/28695596#28695596
Для вашего конкретного примера все типы являются стандартными классами макета и не имеют общих базовых классов или членов (см. Ниже), поэтому вы можете положиться на это поведение в C++11 (и на практике, я думаю, многие компиляторы уже следовали этому правилу, конечно, G++ сделал, и другие после ABI I tanium C++.)
Предупреждение: убедитесь, что у вас нет базовых классов одного типа, потому что они должны быть по разным адресам, например
struct I {};
struct J : I {};
struct K : I { };
struct X { int i; };
struct Y : J, K, X { };
#include <iostream>
Y y;
int main()
{
std::cout << &y << ' ' << &y.i << ' ' << (X*)&y << ' ' << (I*)(J*)&y << ' ' << (I*)(K*)&y << '\n';
}
печатает:
0x600d60 0x600d60 0x600d60 0x600d60 0x600d61
Для типа Y
только один из I
основания могут быть со смещением нуля, поэтому, хотя X
подобъект находится со смещением ноль (т.е. offsetof(Y, i)
ноль) и один из I
базы находится по тому же адресу, но другой I
base (по крайней мере с G++ и Clang ++) один байт в объект, так что если вы получили I*
ты не мог reinterpret_cast
в X*
потому что вы не знаете, какой I
подобъект, на который он указал, I
со смещением 0 или I
по смещению 1.
Это нормально для компилятора поставить второй I
подобъект со смещением 1 (т.е. внутри int
) так как I
не имеет нестатических элементов данных, поэтому вы не можете разыменовать или получать доступ к чему-либо по этому адресу, только получить указатель на объект по этому адресу. Если вы добавили нестатические элементы данных в I
затем Y
больше не будет стандартным макетом и не будет использовать EBO, и offsetof(Y, i)
больше не будет ноль.