Spec-Zone.ru › C++

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

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

Объяснение

Размер любого объекта или подобъекта члена должен быть не менее 1, даже если тип — пустой тип класса (то есть класс или структура без нестатических данных членов), (если не с [[no_unique_address]], см. ниже)(с C++20) для того, чтобы гарантировать, что адреса различных объектов одного типа всегда различаются.

Однако подобъекты базового класса не так ограничены и могут быть полностью исключены из компоновки объекта:

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));
}

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

Типичным примером такой ситуации является наивное реализация std::reverse_iterator (производный от пустого базового класса std::iterator), которая хранит базовый итератор (также производный от std::iterator) в качестве первого нестатического члена данных.

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));
}

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

Оптимизация пустого базового класса требуется для StandardLayoutTypes для поддержания требования, что указатель на объект стандартного расположения, преобразованный с помощью 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, и т. д.) для предотвращения выделения дополнительного места для члена выделения памяти, если выделение памяти является бессостоятельным. Это достигается путем хранения одного из необходимых членов данных (например, begin, end, или указателя capacity для vector) в эквиваленте boost::compressed_pair вместе с выделением памяти.

Ссылки

  • Стандарт C++23 (ISO/IEC 14882:2023):
    • 7.6.10 Операторы равенства [expr.eq]
    • 7.6.2.5 Размер [expr.sizeof]
    • 11 Классы [class]
    • 11.4 Члены класса [class.mem]
  • Стандарт C++20 (ISO/IEC 14882:2020):
    • 7.6.10 Операторы равенства [expr.eq]
    • 7.6.2.4 Размер [expr.sizeof]
    • 11 Классы [class]
    • 11.4 Члены класса [class.mem]
  • Стандарт C++17 (ISO/IEC 14882:2017):
    • 8.10 Операторы равенства [expr.eq]
    • 8.3.3 Размер [expr.sizeof]
    • 12 Классы [class]
    • 12.2 Члены класса [class.mem]
  • Стандарт C++14 (ISO/IEC 14882:2014):
    • 5.10 Операторы равенства [expr.eq]
    • 5.3.3 Размер [expr.sizeof]
    • 9 Классы [class]
    • 9.2 Члены класса [class.mem]
  • Стандарт C++11 (ISO/IEC 14882:2011):
    • 5.10 Операторы равенства [expr.eq] (п: 2)
    • 5.3.3 Размер [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 Размер [expr.sizeof] (п: 2)
    • 9 Классы [class] (п: 3)

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

More C++ Idioms/Empty Base Optimization — Викиучебник

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/language/ebo

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API