C++ 带"Empty classes"的多重继承内存布局



我知道多重继承的内存布局没有定义,所以我不应该依赖它。但是,我可以在特殊情况下依赖它吗?也就是说,一个类只有一个"真正的"超类。所有其他类都是"空类",即既没有字段也没有虚方法的类(即它们只有非虚拟方法)。在这种情况下,这些附加类不应向类的内存布局添加任何内容。 (更简洁地说,在 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吗?

我希望我可以依靠它,因为规范说对象的大小必须至少是一个字节。因此,编译器不能选择其他布局。如果它将布局IJX 的成员后面,那么它们的大小将为零(因为它们没有成员)。因此,唯一合理的选择是对齐所有超类而不进行偏移。

如果我在这里使用reinterpret_cast从IX,我是否正确或我在玩火?

在 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没有非静态数据成员,因此您实际上不能取消引用或访问该地址的任何内容,只能获取指向该地址处对象的指针。 如果将非静态数据成员添加到IY将不再是标准布局,也不必使用 EBO,并且offsetof(Y, i)将不再为零。

最新更新