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

Ermöglicht, dass die Größe eines leeren Basis-Subobjekts null sein kann.

Inhaltsverzeichnis

Erklärung

Die Größe jedes Objekts oder Mitgliedsunterobjekts muss mindestens 1 betragen, selbst wenn der Typ ein leerer Klassentyp ist (d.h. eine Klasse oder Struktur ohne nicht-statische Datenelemente), (sofern nicht mit [[ no_unique_address ]] , siehe unten) (seit C++20) um garantieren zu können, dass die Adressen verschiedener Objekte desselben Typs stets unterschiedlich sind.

Allerdings sind Basisklassen-Subobjekte nicht so eingeschränkt und können vollständig aus dem Objektlayout optimiert werden:

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

Die Optimierung leerer Basisklassen ist untersagt, wenn eine der leeren Basisklassen auch der Typ oder die Basis des Typs des ersten nicht-statischen Datenelements ist, da die beiden Basis-Subobjekte desselben Typs unterschiedliche Adressen innerhalb der Objektrepräsentation des abgeleitetesten Typs haben müssen.

Ein typisches Beispiel für eine solche Situation ist die naive Implementierung von std::reverse_iterator (abgeleitet von der leeren Basisklasse std::iterator ), die den zugrundeliegenden Iterator (ebenfalls abgeleitet von std::iterator ) als ihr erstes nicht-statisches Datenelement speichert.

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

Die Optimierung leerer Basisklassen ist erforderlich für StandardLayoutType s , um die Anforderung aufrechtzuerhalten, dass der Zeiger auf ein Standard-Layout-Objekt, konvertiert mittels reinterpret_cast , auf sein erstes Mitglied zeigt. Dies ist der Grund, warum die Anforderungen für einen Standard-Layout-Typ beinhalten: "hat alle nicht-statischen Datenelemente in derselben Klasse deklariert (entweder alle in der abgeleiteten oder alle in einer Basisklasse)" und "hat keine Basisklassen desselben Typs wie das erste nicht-statische Datenelement".

(since C++11)

Leere Mitgliedssubobjekte dürfen genauso wie leere Basen optimiert werden, wenn sie das Attribut [[ no_unique_address ]] verwenden. Das Nehmen der Adresse eines solchen Mitglieds ergibt eine Adresse, die gleich der Adresse eines anderen Mitglieds desselben Objekts sein kann.

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

Hinweise

Die leere Basisoptimierung wird häufig von allocator-aware Standardbibliotheksklassen verwendet ( std::vector , std::function , std::shared_ptr , etc.), um keinen zusätzlichen Speicherplatz für ihr Allokator-Mitglied zu belegen, falls der Allokator zustandslos ist. Dies wird erreicht, indem eines der erforderlichen Datenmitglieder (z.B. begin , end , oder capacity -Zeiger für den vector ) in einem Äquivalent von boost::compressed_pair mit dem Allokator gespeichert wird.

In MSVC ist die Empty-Base-Optimierung nicht vollständig konform mit den Standardanforderungen ( Why is the empty base class optimization (EBO) is not working in MSVC? ).

Referenzen

  • C++23-Standard (ISO/IEC 14882:2024):
  • 7.6.10 Gleichheitsoperatoren [expr.eq]
  • 7.6.2.5 Sizeof [expr.sizeof]
  • 11 Klassen [class]
  • 11.4 Klassenmitglieder [class.mem]
  • C++20-Standard (ISO/IEC 14882:2020):
  • 7.6.10 Gleichheitsoperatoren [expr.eq]
  • 7.6.2.4 Sizeof [expr.sizeof]
  • 11 Klassen [class]
  • 11.4 Klassenmember [class.mem]
  • C++17-Standard (ISO/IEC 14882:2017):
  • 8.10 Gleichheitsoperatoren [expr.eq]
  • 8.3.3 Sizeof [expr.sizeof]
  • 12 Klassen [class]
  • 12.2 Klassenmitglieder [class.mem]
  • C++14-Standard (ISO/IEC 14882:2014):
  • 5.10 Gleichheitsoperatoren [expr.eq]
  • 5.3.3 Sizeof [expr.sizeof]
  • 9 Klassen [class]
  • 9.2 Klassenmitglieder [class.mem]
  • C++11-Standard (ISO/IEC 14882:2011):
  • 5.10 Gleichheitsoperatoren [expr.eq] (S.: 2)
  • 5.3.3 Sizeof [expr.sizeof] (S.: 2)
  • 9 Klassen [class] (S.: 4,7)
  • 9.2 Klassenmitglieder [class.mem] (S.: 20)
  • C++98 Standard (ISO/IEC 14882:1998):
  • 5.10 Gleichheitsoperatoren [expr.eq] (S: 2)
  • 5.3.3 Sizeof [expr.sizeof] (S: 2)
  • 9 Klassen [class] (S: 3)

Externe Links

More C++ Idioms/Empty Base Optimization — Ein Wikibook