空基类优化(EBO)
  ZXL8ALkrtBPG 2023年11月02日 69 0
c++

空基类优化基础

为保证同一类型的不同对象地址始终不同,要求任何对象或成员子对象的大小至少为 1 个字节,即使这个类为一个空类。

struct A {}; // 只包含类型成员、非虚函数或静态成员变量   
                              
int main()               
{     
    DBG_LOG("%d", sizeof(A)); // 输出为1
}
struct A {};    

struct Derived
{
    int i;
    A a;
};

int main()
{
    DBG_LOG("%d", sizeof(Derived)); // 输出为8
}

当空类作为类成员时,由于空类占用一个字节,并且内存对齐之后则Derived则需要占用8个字节,存在内存浪费;

运用空基类优化之后:

struct A {};

struct Derived : A
{
    int i;
};

int main()
{
    DBG_LOG("%d", sizeof(Derived)); // 输出为4
}

则空类A不会占用额外的内存空间,内存空间上得到优化;

注:空基类优化可简称为EBO (empty base optimization)或者 EBCO (empty base class optimization)

空基类优化失效

如果首个非静态数据成员的类型与一个空基类的类型相同或者由该空基类派生,则会禁用空基类优化【3】:

struct Base {}; // empty class
 
struct Derived1 : Base
{
    int i;
};
 
struct Derived2 : Base
{
    Base c; // Base, occupies 1 byte, followed by padding for i
    int i;
};
 
struct Derived3 : Base
{
    Derived1 c; // derived from Base, occupies sizeof(int) bytes
    int i;
};
 
int main()
{
    // empty base optimization does not apply,
    // base occupies 1 byte, Base member occupies 1 byte
    // followed by 2 bytes of padding to satisfy int alignment requirements
    static_assert(sizeof(Derived2) == 2*sizeof(int));
    
    // empty base optimization does not apply,
    // base takes up at least 1 byte plus the padding
    // to satisfy alignment requirement of the first member (whose
    // alignment is the same as int)
    static_assert(sizeof(Derived3) == 3*sizeof(int));
}

通过clang -Xclang -fdump-record-layouts -std=c++17 -c TestEBO.cpp可以查看上述代码的内存布局如下:

*** Dumping AST Record Layout
         0 | struct Base (empty)
           | [sizeof=1, dsize=1, align=1,
           |  nvsize=1, nvalign=1]

*** Dumping AST Record Layout
         0 | struct Derived2
         0 |   struct Base (base) (empty)
         1 |   struct Base c (empty) // Derive3的空基类优化失效,Base c的地址偏移为1
         4 |   int i                 // 地址填充对齐,所以int i的地址偏移为4
           | [sizeof=8, dsize=8, align=4,
           |  nvsize=8, nvalign=4]

*** Dumping AST Record Layout
         0 | struct Derived1
         0 |   struct Base (base) (empty)
         0 |   int i
           | [sizeof=4, dsize=4, align=4,
           |  nvsize=4, nvalign=4]

*** Dumping AST Record Layout
         0 | struct Derived3
         0 |   struct Base (base) (empty)
         4 |   struct Derived1 c    // Derive3的空基类优化失效,Derive1 c的地址偏移为4
         4 |     struct Base (base) (empty)
         4 |     int i
         8 |   int i
           | [sizeof=12, dsize=12, align=4,
           |  nvsize=12, nvalign=4]

反之,不是首个非静态数据成员的类型

struct Base {}; // empty class
 
struct Derived1 : Base
{
    int i;
};
 
struct Derived2 : Base
{
    int i;
    Base c;
};
 
struct Derived3 : Base
{
    int i;
    Derived1 c; // derived from Base, occupies sizeof(int) bytes
};

对应的内存布局为:

*** Dumping AST Record Layout
         0 | struct Base (empty)
           | [sizeof=1, dsize=1, align=1,
           |  nvsize=1, nvalign=1]

*** Dumping AST Record Layout
         0 | struct Derived2
         0 |   struct Base (base) (empty)
         0 |   int i  // Derived2空基类优化生效
         4 |   struct Base c (empty)
           | [sizeof=8, dsize=5, align=4,
           |  nvsize=5, nvalign=4]

*** Dumping AST Record Layout
         0 | struct Derived1
         0 |   struct Base (base) (empty)
         0 |   int i
           | [sizeof=4, dsize=4, align=4,
           |  nvsize=4, nvalign=4]

*** Dumping AST Record Layout
         0 | struct Derived3
         0 |   struct Base (base) (empty)
         0 |   int i // Derived2空基类优化生效
         4 |   struct Derived1 c
         4 |     struct Base (base) (empty)
         4 |     int i
           | [sizeof=8, dsize=8, align=4,
           |  nvsize=8, nvalign=4]

C++20 空类优化

C++20提供了[[no_unique_address]] 属性提供了组合模式下空类优化:

struct Base {}; // empty class
 
struct Derived1
{
    [[no_unique_address]]Base b;
    int i;
};
 
int main()
{
    static_assert(sizeof(Derived1) == sizeof(int));
}

Derived1 占用内存为4个字节,空类并未占用额外内存。

空基类应用解析(tuple)

在【4】中介绍了std::tuple的应用实践,而stuple实际也应用了空基类优化:

struct Base1 {}; // 空类
struct Base2 {}; // 空类
struct Base3 {}; // 空类

int main()
{
    DBG_LOG("%d %d", sizeof(std::tuple<Base1, Base2, Base3>),
                     sizeof(std::tuple<Base1, Base2, Base3, int>));
}
// 输出为1 4

本节介绍tuple中如何应用EBO。

tuple的模板参数可以支持接收任意类型,熟悉可变模板参数的同学可以快速实现如下代码:

template<typename ...Args>
struct Tuple;
template<>
struct Tuple<> {
};
    
template<typename Head, typename ...Tail>
struct Tuple<Head, Tail...> {
    Head h;
    Tuple<Tail...> t;
};

此时模板参数类型为空类时存在内存浪费;OK,下一步应用EBO优化得到:

template<typename ...Args>
struct Tuple;
template<>
struct Tuple<> {
};
    
template<typename Head, typename ...Tail>
struct Tuple<Head, Tail...> : private Head, Tuple<Tail...> {
};

但Head可能为int或者final类等不可继承类型,因此引入TupleEle:

template<typename T, bool = std::is_class<T>::value && !std::is_final<T>::value>
struct TupleEle;

template<typename T>
struct TupleEle <T, false> {
    T value;
    T& Get() { return value; }
};

template<typename T>
struct TupleEle <T, true> : private T {
    T& Get() { return *this; }
};

template<typename ...Args>
struct Tuple;

template<>
struct Tuple<> {
};

template<typename Head, typename ...Tail>
struct Tuple<Head, Tail...>: private TupleEle<Head>, private Tuple<Tail...> {
};
struct Empty {};
int main()
{
    Tuple<int, Empty> t{};
    DBG_LOG("%d", sizeof(t)); // 4
}

此时如果送入重复类型,则重复继承了TupleEle<xxx>,导致 派生类转换到基类存在歧义,因此进一步修改为:

template<size_t index, typename T, bool = std::is_class<T>::value && !std::is_final<T>::value>
struct TupleEle;

template<size_t index, typename T>
struct TupleEle <index, T, false> {
    T value;
    T& Get() { return value; }
};

template<size_t index, typename T>
struct TupleEle <index, T, true> : private T {
    T& Get() { return *this; }
};

template<typename ...Args>
struct Tuple;
template<>
struct Tuple<> {
};

template<typename Head, typename ...Tail>
struct Tuple<Head, Tail...>: private TupleEle<sizeof...(Tail), Head>, private Tuple<Tail...> {
};

得益于EBO继承关系,在实现Get<xxx>(tuple)利用模板参数推导,可以在常量时间内获取对应元素,补充Get之后的完整代码如下:

template<size_t index, typename T, bool = std::is_class<T>::value && !std::is_final<T>::value>
struct TupleEle;

template<size_t index, typename T>
struct TupleEle <index, T, false> {
    template<typename U>
    TupleEle(U&& u) : value(std::forward<U>(u)) {};
    T& Get() { return value; }
private:
    T value;
};

template<size_t index, typename T>
struct TupleEle <index, T, true> : private T {
    template<typename U>
    TupleEle(U&& u) : T(std::forward<U>(u)) {};
    T& Get() { return *this; }
};

template<typename ...Args>
struct Tuple;
template<>
struct Tuple<> {
};

template<typename Head, typename ...Tail>
struct Tuple<Head, Tail...>: TupleEle<sizeof...(Tail), Head>, private Tuple<Tail...> {
    template<typename H, typename ...Rest>
    Tuple(H&& h, Rest&&...rest) : TupleEle<sizeof...(Tail), Head>(std::forward<H>(h)),
                                  Tuple<Tail...>(std::forward<Rest>(rest)...){}
    template<size_t index, typename ...Ts>
    friend decltype(auto) Get(Tuple<Ts...>& t);
};

template<size_t index, typename T>
T& GetIndex(TupleEle<index, T>& te) { return te.Get(); }

template<size_t index, typename ...Ts>
decltype(auto) Get(Tuple<Ts...>& t) { return GetIndex<sizeof...(Ts) - index -1>(t); }

在GetIndex调用时通过模板参数推导,index确定,推导出对应T;

参考资料

【1】C++20高级编程

【2】C++ Templates

【3】https://en.cppreference.com/w/cpp/language/ebo

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  gBkHYLY8jvYd   2023年11月19日   30   0   0 #includecic++
  ZXL8ALkrtBPG   2023年11月02日   71   0   0 c++
  ZXL8ALkrtBPG   2023年12月06日   27   0   0 字面量c++
  lh6O4DgR0ZQ8   2023年11月24日   18   0   0 cii++c++
  ZXL8ALkrtBPG   2023年11月12日   34   0   0 c++
  gBkHYLY8jvYd   2023年11月19日   27   0   0 十进制高精度c++
  ZXL8ALkrtBPG   2023年11月02日   52   0   0 c++
  ZXL8ALkrtBPG   2023年11月02日   56   0   0 c++
  gBkHYLY8jvYd   2023年12月11日   20   0   0 cic++最小值
  gBkHYLY8jvYd   2023年11月19日   29   0   0 测试点cic++
ZXL8ALkrtBPG