我知道多重继承的内存布局没有定义,所以我不应该依赖它。但是,我可以在特殊情况下依赖它。也就是说,一个班级只有一个“真正的”超级班级。所有其他的都是“空类”,即既没有字段也没有虚拟方法的类(即它们只有非虚方法)。在这种情况下,这些附加类不应该向类的内存布局添加任何内容。 (更简洁地说,在C ++ 11中,该类具有标准布局)
我可以推断出所有超类都没有偏移吗? E.g:
#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
,那么它们的大小将为零(因为它们没有成员)。因此,唯一合理的选择是在没有偏移的情况下对齐所有超类。
如果我在这里使用I
到X
的reinterpret_cast,我是正确还是在玩火?
答案 0 :(得分:9)
在C ++ 11中,编译器需要使用标准布局类型的空基类优化。见https://stackoverflow.com/a/10789707/981959
对于您的特定示例,所有类型都是标准布局类,并且没有公共基类或成员(请参阅下文),因此您可以依赖于C ++ 11中的行为(以及实践中,我认为很多编译器已经遵循了这个规则,当然是G ++做的,而其他编译器遵循Itanium C++ ABI。)
警告:确保您没有任何相同类型的基类,因为它们必须位于不同的地址,例如。
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
基数(至少用G ++和Clang ++)一个字节进入对象,所以如果你得到一个{{1你不能I*
到reinterpret_cast
因为你不知道它所指向的 X*
子对象,I
at偏移0或偏移量为1的I
。
编译器可以将第二个I
子对象放在偏移量1(即里面 I
),因为int
没有非静态数据成员,因此您实际上不能取消引用或访问该地址的任何内容,只能获得指向该地址对象的指针。如果您将非静态数据成员添加到I
,则I
将不再是标准布局,并且不必使用EBO,Y
将不再为零。