c++ 空基类优化

    我们知道c++中一个空类的大小为1个字节,那么如果一个空类作为基类或者成员对象的时候会怎样呢,是不是还是一定占用1个字节呢?

    c++中为保证同一类型的不同对象地址始终有别,要求任何对象或成员子对象,即使该类型是空的类类型(即没有非静态数据成员的 class 或 struct)也是如此。然而,基类子对象不受这种制约,而且可以完全从对象布局中被优化掉,若空基类之一亦为首个非静态数据成员的类型或其类型的基类,则禁用空基优化,因为要求两个同类型基类子对象在最终派生类型的对象表示中必须拥有不同地址。这种情况的典例是 std::reverse_iterator 的实现(派生自空基类 std::iterator),它持有其底层迭代器(亦派生自 std::iterator)为其首个非静态数据成员。

     空基类优化对于标准布局类型 (StandardLayoutType)是被要求的,以维持指向标准布局对象的指针,用 reinterpret_cast 转换后,还指向其首成员,这是标准要求标准布局类型“在同一个类中声明所有非静态数据成员(c++11起 全在派生类或全在某个基类)”,并且“无与首个非静态数据成员同类型的基类”的原因。

#include <cassert>
struct Base {}; // 空类

struct Derived1 : Base {
    int i;
};

struct Derived2 : Base {
    Base c; // Base,占用 1 字节,后随对 i 的填充
    int i;
};

struct Derived3 : Base {
    Derived1 c; // 从 Base 派生,占用 sizeof(int) 字节
    int i;
};

int main()
{
    //TestRank();
    // 任何空类类型的对象大小至少为 1
    printf("sizeof(Base) = %d \n",sizeof(Base));
    // 应用空基优化
    printf("sizeof(Derived1) = %d \n",sizeof(Derived1));
    // 不应用空基优化
    // 基类占用 1 字节,Base 成员占用 1 字节,后随 2 个填充字节以满足 int 的对齐要求
    printf("sizeof(Derived2) = %d \n",sizeof(Derived2));
    // 不应用空基类优化,
    // 基类占用至少 1 字节加填充,以满足首个成员的对齐要求(其对齐要求与 int 相同)
    printf("sizeof(Derived3) = %d \n",sizeof(Derived3));
}

运行结果:

     我们可以看到除了类Derived1对空基类进行了优化,其他的都没有,证明了上面提到的:若空基类之一亦为首个非静态数据成员的类型或其类型的基类,则禁用空基优化,因为要求两个同类型基类子对象在最终派生类型的对象表示中必须拥有不同地址。

     空基类优化常用于具分配器的标准库类(std::vector、std::function、std::shared_ptr 等),使得当分配器无状态时可避免为其分配器成员占用任何额外存储。这是通过将必要的数据成员之一(例如 vector 的 begin、end 或 capacity 指针)与分配器一起,在 boost::compressed_pair 的某种等价物中存储而实现的。

    但是从c++20起,若空成员子对象使用属性 [[no_unique_address]],则允许像空基类一样优化掉它们。取这种成员的地址会产生可能等于同一个对象的某个其他成员的地址。[[no_unique_address]]指示此数据成员不需要具有不同于其类的所有其他非静态数据成员的地址。这表示若该成员拥有空类型(例如无状态分配器),则编译器可将它优化为不占空间,正如同假如它是空基类一样。若该成员非空,则其中的任何尾随填充空间亦可复用于存储其他数据成员。

#include <iostream>
 
struct Empty {}; // 空类
 
struct X {
    int i;
    Empty e;
};
 
struct Y {
    int i;
    [[no_unique_address]] Empty e;
};
 
struct Z {
    char c;
    [[no_unique_address]] Empty e1, e2;
};
 
struct W {
    char c[2];
    [[no_unique_address]] Empty e1, e2;
};
 
int main()
{
    // 任何空类类型对象的大小至少为 1
    static_assert(sizeof(Empty) >= 1);
 
    // 至少需要多一个字节以给 e 唯一地址
    static_assert(sizeof(X) >= sizeof(int) + 1);
 
    // 优化掉空成员
    std::cout << "sizeof(Y) == sizeof(int) is " << std::boolalpha 
              << (sizeof(Y) == sizeof(int)) << '\n';
 
    // e1 与 e2 不能共享同一地址,因为它们拥有相同类型,尽管它们标记有 [[no_unique_address]]。
    // 然而,其中一者可以与 c 共享地址。
    static_assert(sizeof(Z) >= 2);
 
    // e1 与 e2 不能拥有同一地址,但它们之一能与 c[0] 共享,而另一者与 c[1] 共享
    std::cout << "sizeof(W) == 2 is " << (sizeof(W) == 2) << '\n';
}

输出(注意 需支持c++20的编译器才会得到正确的输出,这里我也是截的网上的结果):

   

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值