时间:2011-08-23 12:16:55

标签: c++ pod c++11 standard-layout

我正在调查C++11中新的,轻松的POD定义(第9.7节)

  

标准布局类是一个类:

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

我突出了让我感到惊讶的一些事情。

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

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

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

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

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

6 个答案:

答案 0 :(得分:23)

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

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

C ++ 0x可能可以定义那些东西也是标准布局类型,在这种情况下它也会定义它们的布局方式,与标准布局类型相同 - 布局类型。 Johannes的答案进一步探讨了这一点,看看他的标准布局类的一个很好的属性的例子,这些事情会干扰。

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

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

答案 1 :(得分:21)

您可以将标准布局类对象地址转换为指向其第一个成员的指针,然后通过后面的一个段落返回,这通常也在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 ++并没有规定跨类的数据成员的分配顺序。如果派生类和基类都有数据成员,则标准不会故意定义其地址的顺序,以便为布局内存提供完全的灵活性。但是为了使上面的演员工作,您需要知道分配顺序中的“第一”成员是什么!

  

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

如果基类与第一个数据成员具有相同的类型,那么将基类放在内存中的派生类对象之前的实现需要在内存中派生类对象数据成员之前有一个填充字节(基类会具有大小一),以避免基类和第一个数据成员具有相同的地址(在C ++中,相同类型的两个不同对象始终具有不同的地址)。但是,这也无法将派生类对象的地址转换为其第一个数据成员的类型。

答案 2 :(得分:7)

  

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

当前语言表示编译器无法在同一访问控制下重新排序成员。像:

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

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

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

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

答案 3 :(得分:4)

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

  

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

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

答案 4 :(得分:2)

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

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

答案 5 :(得分:1)

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

这样,Good应该是:

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

未能满足第6个子弹。因此第6弹?