47

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

标准布局类是这样的类:

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

我强调了令我惊讶的部分。

如果我们容忍具有不同访问控制的数据成员会出现什么问题?

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

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

我承认这是一个奇怪的结构,但为什么应该Bad禁止而不是Good

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

4

6 回答 6

25

它基本上是关于与 C++03 和 C 的兼容性:

  • 相同的访问控制 - 允许 C++03 实现使用访问控制说明符作为对类的(组)成员重新排序的机会,例如为了更好地打包它。
  • 层次结构中具有非静态数据成员的多个类 - C++03 没有说明基类的位置,或者是否在相同类型的完整对象中存在的基类子对象中省略了填充。
  • 基类和相同类型的第一个成员 - 由于第二条规则,如果基类类型用于数据成员,则它必须是空类。许多编译器确实实现了空基类优化,因此 Andreas 关于具有相同地址的子对象所说的话是正确的。我不确定标准布局类是什么意思,这意味着基类子对象与相同类型的第一个数据成员具有相同的地址是不好的,但是当基类子对象有与不同类型的第一个数据成员相同的地址。[编辑:这是因为相同类型的不同对象具有不同的地址,即使它们是空的子对象。感谢约翰内斯]

C++0x 可能已经定义了这些东西也是标准布局类型,在这种情况下,它也会定义它们的布局方式,就像它对标准布局类型所做的那样。约翰内斯的回答进一步深入,看看他的例子,这些东西会干扰标准布局类的一个很好的属性。

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

我对如何定义标准布局的理解是,他们研究了可以在不破坏现有实现的情况下放宽哪些 POD 要求。所以我假设没有检查,上面是一些现有的 C++03 实现确实使用类的非 POD 特性来执行与标准布局不兼容的事情的示例。

于 2011-08-23T12:47:59.030 回答
25

您可以将标准布局类对象地址转换为指向其第一个成员的指针,然后由后面的段落中的一个返回,这也经常在 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 指针,因为它不知道它是否是 an 的成员A)。

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

在一个类中,成员按照声明顺序分配在递增的地址中。然而,C++ 并没有规定跨类的数据成员的分配顺序。如果派生类和基类都具有数据成员,则标准不会故意为它们的地址定义顺序,以便在布局内存时为实现提供充分的灵活性。但是要使上述演员表起作用,您需要知道分配顺序中的“第一个”成员是什么!

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

If the base class has the same type as the first data member, implementations that place the base classes before the derived class objects in memory would need to have a padding byte before the derived class object data members in memory (base class would have size one), to avoid having the same address for both the base class and the first data member (in C++, two distinct objects of the same type always have different addresses). But that would again make impossible to cast the address of the derived class object to the type of its first data member.

于 2011-08-23T19:45:20.410 回答
8

如果我们容忍具有不同访问控制的数据成员会出现什么问题?

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

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

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

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

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

于 2011-08-23T12:50:54.227 回答
4

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

这确保了具有相同类类型且属于相同最派生对象的两个子对象不会分配在相同地址。

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

于 2011-08-23T12:30:07.133 回答
2

从第 5 条开始,似乎两者都是非 pod,因为最派生类具有非静态数据成员(int),它不能具有具有非静态数据成员的基类。

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

于 2011-08-23T12:26:16.790 回答
1

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

这样,Good 应该是:

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

未能满足第 6 条子弹。因此是第 6 颗子弹?

于 2011-08-23T12:31:03.580 回答