我知道多重继承的内存布局没有定义,所以我不应该依赖它。但是,我可以在特殊情况下依赖它吗?也就是说,一个类只有一个"真正的"超类。所有其他类都是"空类",即既没有字段也没有虚方法的类(即它们只有非虚拟方法)。在这种情况下,这些附加类不应向类的内存布局添加任何内容。 (更简洁地说,在 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
是唯一真正的基类。程序的输出(使用 g++4.6 在 linux 上编译时)如下:
8
0x233f010
0x233f010
0x233f010
0x233f010
正如我总结的那样,没有指针调整。但是这个实现是特定的还是我可以依赖它。即,如果我收到一个类型 I
的对象(并且我知道只有这些类存在),我可以使用reinterpret_cast
将其转换为X
吗?
我希望我可以依靠它,因为规范说对象的大小必须至少是一个字节。因此,编译器不能选择其他布局。如果它将布局I
并J
在 X
的成员后面,那么它们的大小将为零(因为它们没有成员)。因此,唯一合理的选择是对齐所有超类而不进行偏移。
如果我在这里使用reinterpret_cast从I
到X
,我是否正确或我在玩火?
在 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++ 中)是对象中的一个字节,所以如果你得到一个I*
你不能reinterpret_cast
X*
,因为你不知道它指向哪个I
子对象, 偏移量为 0 处的I
或偏移量为 1 处的I
。
编译器可以将第二个I
子对象放在偏移量 1 处(即在int
内),因为I
没有非静态数据成员,因此您实际上不能取消引用或访问该地址的任何内容,只能获取指向该地址处对象的指针。 如果将非静态数据成员添加到I
则Y
将不再是标准布局,也不必使用 EBO,并且offsetof(Y, i)
将不再为零。