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

Permite que el tamaño de un subobjeto base vacío sea cero.

Contenidos

Explicación

El tamaño de cualquier objeto o subobjeto miembro debe ser al menos 1 incluso si el tipo es una class type vacía (es decir, una clase o struct que no tiene miembros de datos no estáticos), (a menos que se use [[ no_unique_address ]] , ver más abajo) (since C++20) para poder garantizar que las direcciones de objetos distintos del mismo tipo sean siempre diferentes.

Sin embargo, los subobjetos de la clase base no tienen esta restricción y pueden ser completamente optimizados en el diseño del objeto:

struct Base {}; // empty class
struct Derived1 : Base
{
    int i;
};
int main()
{
    // the size of any object of empty class type is at least 1
    static_assert(sizeof(Base) >= 1);
    // empty base optimization applies
    static_assert(sizeof(Derived1) == sizeof(int));
}

La optimización de base vacía está prohibida si una de las clases base vacías es también el tipo o la base del tipo del primer miembro de datos no estático, ya que se requiere que los dos subobjetos base del mismo tipo tengan direcciones diferentes dentro de la representación del objeto del tipo más derivado.

Un ejemplo típico de tal situación es la implementación ingenua de std::reverse_iterator (derivada de la clase base vacía std::iterator ), que mantiene el iterador subyacente (también derivado de std::iterator ) como su primer miembro de datos no estático.

struct Base {}; // clase vacía
struct Derived1 : Base
{
    int i;
};
struct Derived2 : Base
{
    Base c; // Base, ocupa 1 byte, seguido de relleno para i
    int i;
};
struct Derived3 : Base
{
    Derived1 c; // derivada de Base, ocupa sizeof(int) bytes
    int i;
};
int main()
{
    // la optimización de base vacía no se aplica,
    // la base ocupa 1 byte, el miembro Base ocupa 1 byte
    // seguido de 2 bytes de relleno para satisfacer los requisitos de alineación de int
    static_assert(sizeof(Derived2) == 2*sizeof(int));
    // la optimización de base vacía no se aplica,
    // la base ocupa al menos 1 byte más el relleno
    // para satisfacer el requisito de alineación del primer miembro (cuya
    // alineación es la misma que int)
    static_assert(sizeof(Derived3) == 3*sizeof(int));
}

La optimización de base vacía es requerida para los StandardLayoutType s para mantener el requisito de que el puntero a un objeto de diseño estándar, convertido usando reinterpret_cast , apunte a su miembro inicial, razón por la cual los requisitos para un tipo de diseño estándar incluyen "tiene todos los miembros de datos no estáticos declarados en la misma clase (todos en la derivada o todos en alguna base)" y "no tiene clases base del mismo tipo que el primer miembro de datos no estático".

(desde C++11)

Se permite optimizar los subobjetos de miembro vacíos al igual que las bases vacías si utilizan el atributo [[ no_unique_address ]] . Tomar la dirección de dicho miembro resulta en una dirección que puede ser igual a la dirección de algún otro miembro del mismo objeto.

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));
}
(desde C++20)

Notas

La optimización de base vacía es comúnmente utilizada por las clases de la biblioteca estándar conscientes del asignador ( std::vector , std::function , std::shared_ptr , etc) para evitar ocupar almacenamiento adicional para su miembro asignador si el asignador no tiene estado. Esto se logra almacenando uno de los miembros de datos requeridos (por ejemplo, begin , end , o capacity pointer para el vector ) en un equivalente de boost::compressed_pair con el asignador.

En MSVC, la optimización de clases base vacías no es completamente conforme con los requisitos del estándar ( Why is the empty base class optimization (EBO) is not working in MSVC? ).

Referencias

  • Estándar C++23 (ISO/IEC 14882:2024):
  • 7.6.10 Operadores de igualdad [expr.eq]
  • 7.6.2.5 Sizeof [expr.sizeof]
  • 11 Clases [class]
  • 11.4 Miembros de clase [class.mem]
  • Estándar C++20 (ISO/IEC 14882:2020):
  • 7.6.10 Operadores de igualdad [expr.eq]
  • 7.6.2.4 Sizeof [expr.sizeof]
  • 11 Clases [class]
  • 11.4 Miembros de clase [class.mem]
  • Estándar C++17 (ISO/IEC 14882:2017):
  • 8.10 Operadores de igualdad [expr.eq]
  • 8.3.3 Sizeof [expr.sizeof]
  • 12 Clases [class]
  • 12.2 Miembros de clase [class.mem]
  • Estándar C++14 (ISO/IEC 14882:2014):
  • 5.10 Operadores de igualdad [expr.eq]
  • 5.3.3 Sizeof [expr.sizeof]
  • 9 Clases [class]
  • 9.2 Miembros de clase [class.mem]
  • Estándar C++11 (ISO/IEC 14882:2011):
  • 5.10 Operadores de igualdad [expr.eq] (p: 2)
  • 5.3.3 Sizeof [expr.sizeof] (p: 2)
  • 9 Clases [class] (p: 4,7)
  • 9.2 Miembros de clase [class.mem] (p: 20)
  • Estándar C++98 (ISO/IEC 14882:1998):
  • 5.10 Operadores de igualdad [expr.eq] (p: 2)
  • 5.3.3 Sizeof [expr.sizeof] (p: 2)
  • 9 Clases [class] (p: 3)

Enlaces externos

More C++ Idioms/Empty Base Optimization — Un wikilibro