Namespaces
Variants

Empty base optimization

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

允许空基类子对象的大小为零。

目录

说明

任何 对象 或成员子对象的大小必须至少为1,即使该类型是空的 类类型 (即没有非静态数据成员的类或结构体), (除非使用 [[ no_unique_address ]] ,详见下文) (C++20 起) ,这是为了确保同一类型的不同对象的地址始终不同。

然而,基类子对象不受此约束,可以被完全优化出对象布局:

struct Base {}; // 空类
struct Derived1 : Base
{
    int i;
};
int main()
{
    // 任何空类类型对象的大小至少为1
    static_assert(sizeof(Base) >= 1);
    // 空基类优化生效
    static_assert(sizeof(Derived1) == sizeof(int));
}

当某个空基类同时也是第一个非静态数据成员的类型或其基类时,禁止进行空基类优化,因为在最终派生类型的对象表示中,要求同一类型的两个基类子对象必须具有不同地址。

这种情况的一个典型例子是 std::reverse_iterator 的简单实现(派生自空基类 std::iterator ),该实现将底层迭代器(同样派生自 std::iterator )作为其第一个非静态数据成员保存。

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()
{
    // 空基类优化不适用:
    // 基类占1字节,Base 成员占1字节
    // 后接2字节填充以满足 int 对齐要求
    static_assert(sizeof(Derived2) == 2*sizeof(int));
    // 空基类优化不适用:
    // 基类至少占1字节加上为满足首个成员
    //(其对齐要求与 int 相同)对齐所需的填充字节
    static_assert(sizeof(Derived3) == 3*sizeof(int));
}

对于 标准布局类型 ,必须进行空基类优化,以确保将标准布局对象指针使用 reinterpret_cast 转换后指向其首成员的要求得以维持。这就是为什么标准布局类型的要求包含“所有非静态数据成员必须在同一个类中声明(要么全部在派生类中,要么全部在某个基类中)”以及“没有与第一个非静态数据成员类型相同的基类”。

(C++11 起)

若空成员子对象使用属性 [[ no_unique_address ]] ,则允许像空基类一样将其优化掉。获取此类成员的地址可能得到与同一对象中其他成员地址相等的地址。

struct Empty {}; // empty class
struct X
{
    int i;
    [[no_unique_address]] Empty e;
};
int main()
{
    // the size of any object of empty class type is at least 1
    static_assert(sizeof(Empty) >= 1);
    // empty member optimized out:
    static_assert(sizeof(X) == sizeof(int));
}
(C++20 起)

注释

空基类优化通常被分配器感知的标准库类( std::vector std::function std::shared_ptr 等)所采用,以避免当其分配器为无状态时占用额外的存储空间。这是通过将所需数据成员之一(例如 vector begin end capacity 指针)存储在与 boost::compressed_pair 等效的结构中并与分配器共同实现的。

在MSVC中,空基类优化并未完全符合标准要求( 为何空基类优化(EBO)在MSVC中无法生效? )。

参考文献

  • C++23 标准 (ISO/IEC 14882:2024):
  • 7.6.10 相等运算符 [expr.eq]
  • 7.6.2.5 Sizeof 运算符 [expr.sizeof]
  • 11 类 [class]
  • 11.4 类成员 [class.mem]
  • C++20 标准 (ISO/IEC 14882:2020):
  • 7.6.10 相等运算符 [expr.eq]
  • 7.6.2.4 Sizeof 运算符 [expr.sizeof]
  • 11 类 [class]
  • 11.4 类成员 [class.mem]
  • C++17 标准 (ISO/IEC 14882:2017):
  • 8.10 相等运算符 [expr.eq]
  • 8.3.3 Sizeof 运算符 [expr.sizeof]
  • 12 类 [class]
  • 12.2 类成员 [class.mem]
  • C++14 标准 (ISO/IEC 14882:2014):
  • 5.10 相等运算符 [expr.eq]
  • 5.3.3 Sizeof 运算符 [expr.sizeof]
  • 9 类 [class]
  • 9.2 类成员 [class.mem]
  • C++11 标准 (ISO/IEC 14882:2011):
  • 5.10 相等运算符 [expr.eq] (页: 2)
  • 5.3.3 Sizeof 运算符 [expr.sizeof] (页: 2)
  • 9 类 [class] (页: 4,7)
  • 9.2 类成员 [class.mem] (页: 20)
  • C++98 标准 (ISO/IEC 14882:1998):
  • 5.10 相等运算符 [expr.eq] (页: 2)
  • 5.3.3 Sizeof 运算符 [expr.sizeof] (页: 2)
  • 9 类 [class] (页: 3)

外部链接

More C++ Idioms/Empty Base Optimization — 维基教科书