是否将基类的填充复制到派生类中?

时间:2018-10-15 13:57:45

标签: c++

最近,我一直在阅读“在c ++对象模型内部”。它说,如果要将基类分配给派生类,则还应该将基类中使用的填充复制到派生类中。因此,我在64位计算机上运行测试:

    class A {
    public:
        int valA;
        char a;
    };
    class B : public A {
    public:
        char b;
    };
    class C : public B {
    public:
        char c;
    };
    int main(){
        std::cout << sizeof(A) << " " << sizeof(B) << " " << sizeof(C) 
        << std::endl;
        C c;
        printf("%p\n%p\n%p\n",&c,&c.b,&c.c);
    }

这是结果:

    8 12 12

    0x7ffd22c5072c

    0x7ffd22c50734

    0x7ffd22c50735

那么C为什么和B一样大小?尽管似乎B在A中使用了3个字节的填充。

2 个答案:

答案 0 :(得分:2)

  

那么C为什么和B一样大小?

因为B已重用了C::b的尾随填充。因为B不是POD(普通旧数据)类(因为它不是标准布局类),所以可以重复使用填充。

  

尽管B似乎在A中使用了3个字节的填充。

A的填充不能再用于B的其他子对象,因为A是标准布局类,并且可以复制,即A是POD类


  

基类的填充是否会复制到派生类中?

我想您不是要询问复制,而是询问派生类的基类子对象是否具有与单个类型相同的填充。

答案可能是从上面得出的:填充将是相同的,除了可以将尾部填充重新用于其他子对象,除非基类是POD,在这种情况下其填充不能可以重复使用。

在可以重复使用填充的情况下,标准未指定是否填充,并且编译器之间存在差异。


  

请解释或链接到“标准布局类型”的定义。

当前标准草案:

  

[基本类型]

     

...标量类型,标准布局类类型([class.prop]),此类类型的数组以及这些类型的cv限定版本统称为标准布局类型。

     
     

[class.prop](在标准的较旧版本中,可以直接在[class]下找到它们)

     

如果满足以下条件,则S类为标准布局类:

     
      
  • (3.1)没有非标准布局类(或此类数组)或引用的非静态数据成员,

  •   
  • (3.2)没有虚函数,也没有虚基类,

  •   
  • (3.3)对所有非静态数据成员具有相同的访问控制,

  •   
  • (3.4)没有非标准布局的基类,

  •   
  • (3.5)最多具有一个任何给定类型的基类子对象,

  •   
  • (3.6)具有该类中的所有非静态数据成员和位字段,以及在同一类中首先声明的基类,并且

  •   
  • (3.7)没有类型集合M(S)的元素作为基类,对于任何类型X,M(X)的定义如下。107[注意:M(X)为的   可能位于某个位置的所有非基类子对象的类型的集合   X中的零偏移。—注释]

         
        
    • (3.7.1)如果X是不包含(可能是继承的)非静态数据成员的非联合类类型,则集合M(X)为空。

    •   
    • (3.7.2)如果X是具有0大小或第一个零大小的X0类型的非静态数据成员的非联合类类型   X的非静态数据成员(其中所述成员可以是匿名的   联合),则集合M(X)由X0和M(X0)的元素组成。

    •   
    • (3.7.3)如果X是联合类型,则集合M(X)是所有M(Ui)和包含所有Ui的集合的联合,其中每个Ui是集合的类型。   X的第ith个非静态数据成员。

    •   
    • (3.7.4)如果X是元素类型为Xe的数组类型,则集合M(X)由Xe和M(Xe)的元素组成。

    •   
    • (3.7.5)如果X是非类,非数组类型,则集合M(X)为空。

    •   
  •   

项目(3.6)在这种情况下适用。 B的某些成员未在B中首先声明。特别是B :: A :: valA和B :: A :: a首先在A中声明。一种更友好的描述规则的方法是:该类必须具有没有直接成员,或者其祖先都没有成员。在这种情况下,基类和派生类都具有成员,因此它不是标准布局。

答案 1 :(得分:0)

CB的大小相同,因为ABI在平台上选择使用B中的填充来存储1字节成员C::cB的末尾有3个字节的填充,因为整个B对象的对齐方式为4(由于int中的A成员)。

BA的大小不同,因为在这种情况下,ABI显然不允许将B::b存储在A的填充中是房间。当所有A成员都公开时,就会发生这种情况,如您的示例所示:如果将任何成员设为私有,则ABC的大小将全部是8。我相信这可能是为了ABI向后兼容,而不是出于标准中任何语言的推动。

我不知道标准中是否有直接通过 允许的语言(但不需要这样做),但是显然这种填充重复使用是在继承的情况下。例如,std::memcpy的文档说:

如果对象可能重叠或不可TriviallyCopyable,则未指定memcpy的行为,并且可能未定义。

接着定义potentially-overlapping

子对象可能重叠(如果存在)

  • 基类子对象,或
  • 使用[[no_unique_address]]属性声明的非静态数据成员。

第二个条件仅适用于C ++ 20。

这似乎是为了允许共享填充而编写的:如果此子句不存在,指向memcpy的{​​{1}}子类的指针上的B将会覆盖C存储在通常用于C::c的填充中。