我知道多重继承的内存布局没有定义,所以我不应该依赖它。但是,在特殊情况下我可以依靠它吗?也就是说,一个类只有一个“真正的”父类(super class)。所有其他的都是“空类”,即既没有字段也没有虚方法的类(即它们只有非虚方法)。在这种情况下,这些额外的类不应向类的内存布局添加任何内容。 (更简洁一点,在C++11的写法中,类有standard-layout)
我可以推断所有父类(super class)都没有偏移量吗?例如:
#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
的成员后面,那么它们的大小将为零(因为它们没有成员)。因此,唯一合理的选择是将所有父类(super class)无偏移对齐。
如果我在这里使用从 I
到 X
的 reinterpret_cast,我是正确的还是在玩火?
最佳答案
在 C++11 中,编译器需要为标准布局 类型使用空基类优化。见https://stackoverflow.com/a/10789707/981959
对于您的特定示例,所有类型都是标准布局类,没有公共(public)基类或成员(见下文),因此您可以依赖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
或 I
在偏移量 1 处。
编译器可以将第二个 I
子对象放在偏移量 1 处(即 inside int
),因为 I
没有非静态数据成员,因此您实际上不能取消引用或访问该地址的任何内容,只能获取指向该地址对象的指针。如果您向 I
添加非静态数据成员,则 Y
将不再是标准布局,并且不必使用 EBO,并且 offsetof(Y, i )
将不再为零。
关于 "Empty classes"的 C++ 多重继承内存布局,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11048045/