为什么 C++11 的 POD "standard layout"定义是这样的?



我正在研究c++ 11(第9.7节)中新的、宽松的POD定义

标准布局类是这样一个类:

  • 没有非标准布局类(或此类类型的数组)或引用类型的非静态数据成员,
  • 没有虚函数(10.3)和虚基类(10.1),
  • 对所有非静态数据成员具有相同的访问控制(第11条),
  • 没有非标准布局基类,
  • 在大多数派生类中没有非静态数据成员,并且最多有一个非静态数据成员的基类,或者没有具有非静态数据成员的基类,并且
  • 没有与第一个非静态数据成员相同类型的基类。

我把那些让我惊讶的部分都标出来了。

如果允许数据成员具有不同的访问控制,会出现什么问题?

如果第一个数据成员也是基类,会出现什么问题?例如

struct Foo {};
struct Good : Foo {int x; Foo y;};
struct Bad  : Foo {Foo y; int x;};

我承认这是一个奇怪的结构,但是为什么Bad被禁止而Good却不被禁止呢?

最后,如果多个组成类有数据成员,会出现什么问题?

您可以将标准布局类对象地址强制转换为指向其第一个成员的指针,并在后面的一个段落中返回,这在C中也经常这样做:

struct A { int x; };
A a;
// "px" is guaranteed to point to a.x
int *px = (int*) &a;
// guaranteed to point to a
A *pa = (A*)px; 

要使其工作,第一个成员和完整对象必须具有相同的地址(编译器不能调整int指针的任何字节,因为它不知道它是否是A的成员)。

最后,如果多个组成类有数据成员,会出现什么问题?

在类中,成员按声明顺序按递增的地址分配。然而,c++并没有规定跨类数据成员的分配顺序。如果派生类和基类都有数据成员,则标准不会故意为它们的地址定义顺序,以便在布局内存时给予实现充分的灵活性。但是要使上面的强制转换工作,您需要知道分配顺序中的"第一个"成员是什么!

如果第一个数据成员也是基类,会出现什么问题?

如果基类与第一个数据成员具有相同的类型,则在内存中将基类置于派生类对象之前的实现需要在内存中派生类对象数据成员之前有一个填充字节(基类的大小为1),以避免基类和第一个数据成员具有相同的地址(在c++中,相同类型的两个不同对象总是具有不同的地址)。但是,这将再次导致无法将派生类对象的地址强制转换为其第一个数据成员的类型。

基本上是关于c++ 03和C:

的兼容性
  • 相同的访问控制- c++ 03实现允许使用访问控制说明符作为重新排序类(组)成员的机会,例如为了更好地打包它。
  • 具有非静态数据成员的层次结构中的多个类- c++ 03没有说明基类位于何处,或者是否在基类子对象中省略填充,这些填充将出现在相同类型的完整对象中。
  • 基类和同一类型的第一个成员—因为第二条规则,如果基类类型用于数据成员,那么它必须是空类。许多编译器确实实现了空基类优化,所以Andreas所说的具有相同地址的子对象是正确的。我不确定,虽然它是关于标准布局类,这意味着它是坏的基类子对象具有相同的地址作为同一类型的第一个数据成员,但这并不重要,当基类子对象具有相同的地址作为不同类型的第一个数据成员。[编辑:这是因为相同类型的不同对象有不同的地址,即使它们是空子对象。]感谢Johannes]

c++ 0x可能也可以定义这些东西是标准布局类型,在这种情况下,它也可以定义它们如何布局,就像标准布局类型一样。Johannes的回答更深入,看看他的例子,标准布局类的一个很好的属性,这些东西会干扰。

但是如果它这样做了,那么一些实现将被迫改变它们如何布局类来匹配新的要求,这对于不同版本的编译器在c++ 0x之前和之后的结构兼容性是一个麻烦。它基本上破坏了c++的ABI。

我对标准布局如何定义的理解是,他们考虑了在不破坏现有实现的情况下可以放松哪些POD需求。所以我假设没有检查,上面的例子是一些现有的c++ 03实现使用类的非pod性质来做一些与标准布局不兼容的事情。

如果允许数据成员具有不同的访问控制,会出现什么问题?

当前语言规定编译器不能在相同的访问控制下对成员重新排序。如:

struct x
{
public:
    int x;
    int y;
private:
    int z;
};

这里x必须在y之前分配,但是z相对于x和y没有限制。

struct y
{
public:
    int x;
public:
    int y;
};

新的措辞说,尽管有两个public, y仍然是POD。这实际上是对规则的放松。

至于为什么不允许使用Bad,让我引用我发现的一篇文章:

这确保两个子对象具有相同的类类型和属于同一个派生最多的对象的属性,不会在相同地址。

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2172.html

从子弹5,似乎两者都是非pod,因为大多数派生类具有非静态数据成员(int),它不能具有非静态数据成员的基类。

我理解为:"只有一个"基"类(即类本身或它继承的类之一)可以有非静态数据成员"

struct Good也不是标准布局,因为Foo和Good有非静态数据成员。

这样,Good应该是:

struct Foo {int foo;};
struct Good : public Foo {Foo y;};

不能满足第六项。所以才会有第六颗子弹?

相关内容

最新更新