8

我知道多重继承的内存布局没有定义,所以我不应该依赖它。但是,我可以在特殊情况下依赖它吗?也就是说,一个类只有一个“真正的”超类。所有其他都是“空类”,即既没有字段也没有虚拟方法的类(即它们只有非虚拟方法)。在这种情况下,这些额外的类不应向类的内存布局添加任何内容。(更简洁地说,在 C++11 的措辞中,该类具有standard-layout

我可以推断所有超类都没有偏移吗?例如:

#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;
}

这里,YX唯一真正基类的类。程序的输出(在linux上用g++4.6编译时)如下:

8

0x233f010

0x233f010

0x233f010

0x233f010

正如我得出的结论,没有指针调整。但是这个实现是特定的还是我可以依赖它。即,如果我收到一个类型的对象I(并且我知道只存在这些类),我可以使用 areinterpret_cast将其转换为X吗?

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

I如果我从这里使用 reinterpret_cast ,我是正确的还是在玩火X

4

1 回答 1

11

In C++11 the compiler is required to use the Empty Base-class Optimization for standard layout types. see https://stackoverflow.com/a/10789707/981959

For your specific example all the types are standard layout classes and don't have common base classes or members (see below) so you can rely on that behaviour in C++11 (and in practice, I think many compilers already followed that rule, certainly G++ did, and others following the Itanium C++ ABI.)

A caveat: make sure you don't have any base classes of the same type, because they must be at distinct addresses, e.g.

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';

}

prints:

0x600d60 0x600d60 0x600d60 0x600d60 0x600d61

For the type Y only one of the I bases can be at offset zero, so although the X sub-object is at offset zero (i.e. offsetof(Y, i) is zero) and one of the I bases is at the same address, but the other I base is (at least with G++ and Clang++) one byte into the object, so if you got an I* you couldn't reinterpret_cast to X* because you wouldn't know which I sub-object it pointed to, the I at offset 0 or the I at offset 1.

It's OK for the compiler to put the second I sub-object at offset 1 (i.e. inside the int) because I has no non-static data members, so you can't actually dereference or access anything at that address, only get a pointer to the object at that address. If you added non-static data members to I then Y would no longer be standard layout and would not have to use the EBO, and offsetof(Y, i) would no longer be zero.

于 2012-06-15T11:02:31.960 回答