close
Пространства имён
Варианты
Действия

Оптимизация пустого базового класса

Материал из cppreference.com
 
 
 
 

Позволяет размеру пустого базового подобъекта быть равным нулю.

Объяснение

Размер любого объекта или подобъекта-элемента должен быть не менее 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));
}

Если происходит множественное наследование, то конкретные оптимизации зависят от компилятора. В MSVC оптимизация нулевого базового класса применяется только с последнему нулевому базовому классу, к остальным нулевым базовым классам нулевая базовая оптимизация не применяется, и выделяется один байт. В GCC, независимо от того, сколько существует пустых базовых классов, оптимизация пустого базового класс применяет к пустым базовым классам без выделения памяти, а адрес пустого базового класса совпадает с адресом первого объекта производного класса.

Оптимизация пустой базы требуется для StandardLayoutType, чтобы сохранить требование, когда указатель на объект стандартной компоновки, преобразованный с использованием reinterpret_cast, указывал на его начальный элемент, поэтому требования к стандартному типу компоновки включают "все нестатические элементы данных, объявленные в одном и том же классе (либо все в производном, либо все в некотором базовом)" и "не имеет базовых классов того же типа, что и первый нестатический элемент данных".

(начиная с C++11)

Пустые подобъекты-элементы можно оптимизировать так же, как и пустые базовые классы, если они используют атрибут [[no_unique_address]]. Взятие адреса такого элемента приводит к адресу, который может совпадать с адресом какого-либо другого элемента того же объекта.

struct Empty {}; // empty class

struct X {
    int i;
    [[no_unique_address]] Empty e;
};

int main()
{
    // размер любого объекта пустого классового типа не менее 1
    static_assert(sizeof(Empty) >= 1);

    // пустой элемент оптимизирован:
    static_assert(sizeof(X) == sizeof(int));
}
(начиная с C++20)

Примечание

Оптимизация пустой базы обычно используется классами стандартной библиотеки с поддержкой аллокатора (std::vector, std::function, std::shared_ptr и т.д.) чтобы не занимать какую-либо дополнительную память для своего элемента аллокатора, если аллокатор не имеет состояния. Это достигается путём сохранения одного из необходимых элементов данных (например, указателя begin, end или capacity для vector) в эквивалент boost::compressed_pair с распределителем.

Ссылки

  • C++23 стандарт (ISO/IEC 14882:2023):
  • 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)

Внешние ссылки

  Дополнительные Идиомы C++/Оптимизация Пустой Базы — Викикнига