empty_bases

Microsoft-spezifisch

Der C++-Standard erfordert, dass ein am häufigsten abgeleitetes Objekt eine Nicht-Null-Größe aufweisen muss und mindestens ein Bytes Speicher belegen muss. Da sich die Anforderung nur auf die meisten abgeleiteten Objekte erstreckt, unterliegen Basisklassenunterobjekte dieser Einschränkung nicht. Die Leere Basisklassenoptimierung (EMPTY Base Class Optimization, EBCO) nutzt diese Freiheit. Dies führt zu einer reduzierten Arbeitsspeicherauslastung, wodurch die Leistung verbessert werden kann. Der Microsoft Visual C++-Compiler hatte in der Vergangenheit nur eingeschränkte Unterstützung für EBCO. In Visual Studio 2015 Update 3 und höher haben wir ein neues __declspec(empty_bases) Attribut für Klassentypen hinzugefügt, das diese Optimierung vollständig nutzt.

Wichtig

Die Verwendung kann __declspec(empty_bases) zu einer ABI-unterbrechungsbedingten Änderung des Struktur- und Klassenlayouts führen, auf die sie angewendet wird. Stellen Sie sicher, dass der gesamte Clientcode dieselben Definitionen für Strukturen und Klassen wie Ihren Code verwendet, wenn Sie dieses Speicherklassen-Attribut verwenden.

Syntax

__declspec( empty_bases )

Hinweise

In Visual Studio ist eine leere Klasse ohne Angabe __declspec(align())alignas() 1 Byte groß:

struct Empty1 {};
static_assert(sizeof(Empty1) == 1, "Empty1 should be 1 byte");

Eine Klasse mit einem einzelnen nicht statischen Datenmemmemm ist char ebenfalls 1 Byte groß:

struct Struct1
{
  char c;
};
static_assert(sizeof(Struct1) == 1, "Struct1 should be 1 byte");

Das Kombinieren dieser Klassen in einer Klassenhierarchie führt auch zu einer Klasse mit einer Größe von 1 Byte:

struct Derived1 : Empty1
{
  char c;
};
static_assert(sizeof(Derived1) == 1, "Derived1 should be 1 byte");

Dieses Ergebnis ist die leere Basisklassenoptimierung bei der Arbeit, wie es ohne Derived1 2 Bytes in der Größe wäre: 1 Byte für Empty1 und 1 Byte für Derived1::c. Das Klassenlayout ist auch optimal, wenn eine Kette leerer Klassen vorhanden ist:

struct Empty2 : Empty1 {};
struct Derived2 : Empty2
{
  char c;
};
static_assert(sizeof(Derived2) == 1, "Derived2 should be 1 byte");

Das Standardklassenlayout in Visual Studio nutzt EBCO jedoch nicht in mehreren Vererbungsszenarien:

struct Empty3 {};
struct Derived3 : Empty2, Empty3
{
  char c;
};
static_assert(sizeof(Derived3) == 1, "Derived3 should be 1 byte"); // Error

Obwohl Derived3 es sich bei 1 Byte um eine Größe handeln könnte, führt das Standardklassenlayout dazu, dass es 2 Byte groß ist. Der Klassenlayoutalgorithmus fügt 1 Byte Abstand zwischen zwei aufeinander folgenden leeren Basisklassen hinzu, wodurch Empty2 ein zusätzliches Byte innerhalb Derived3von :

class Derived3  size(2):
   +---
0  | +--- (base class Empty2)
0  | | +--- (base class Empty1)
   | | +---
   | +---
1  | +--- (base class Empty3)
   | +---
1  | c
   +---

Die Auswirkungen dieses suboptimalen Layouts werden zusammengesetzt, wenn die Ausrichtungsanforderungen einer späteren Basisklasse oder eines Memberunterobjekts zusätzlichen Abstand erzwingen:

struct Derived4 : Empty2, Empty3
{
  int i;
};
static_assert(sizeof(Derived4) == 4, "Derived4 should be 4 bytes"); // Error

Die natürliche Ausrichtung für ein Objekt vom Typ int beträgt 4 Byte, sodass 3 Bytes zusätzlicher Abstand nach Empty3 der korrekten Ausrichtung Derived4::ihinzugefügt werden müssen:

class Derived4 size(8):
   +---
0  | +--- (base class Empty2)
0  | | +--- (base class Empty1)
   | | +---
   | +---
1  | +--- (base class Empty3)
   | +---
   | <alignment member> (size=3)
4  | i
   +---

Ein weiteres Problem mit dem Standardklassenlayout besteht darin, dass eine leere Basisklasse an einem Offset über das Ende der Klasse angeordnet werden kann:

struct Struct2 : Struct1, Empty1
{
};
static_assert(sizeof(Struct2) == 1, "Struct2 should be 1 byte");
class Struct2 size(1):
   +---
0  | +--- (base class Struct1)
0  | | c
   | +---
1  | +--- (base class Empty1)
   | +---
   +---

Obwohl Struct2 die optimale Größe ist, wird bei Offset 1 innerhalb Struct2 der Größe angeordnet, Empty1 aber die Größe Struct2 wird nicht erhöht, um sie zu berücksichtigen. Daher ist die Adresse des Unterobjekts A[0] für ein Array A von Struct2 Objekten identisch mit der Adresse von A[1], die nicht der Fall sein sollte.Empty1 Dieses Problem würde nicht auftreten, wenn Empty1 sie bei Offset 0 innerhalb Struct2des Bereichs angeordnet wurden, wodurch das Struct1 Unterobjekt überlappen würde.

Der Standardlayoutalgorithmus wurde nicht geändert, um diese Einschränkungen zu beheben und EBCO vollständig zu nutzen. Eine solche Änderung würde die binäre Kompatibilität unterbrechen. Wenn das Standardlayout für eine Klasse aufgrund von EBCO geändert wurde, müssen alle Objektdateien und -bibliotheken, die die Klassendefinition enthalten, neu kompiliert werden, damit sie alle dem Klassenlayout zustimmen. Diese Anforderung würde sich auch auf Bibliotheken aus externen Quellen erstrecken. Die Entwickler solcher Bibliotheken müssten unabhängige Versionen bereitstellen, die sowohl mit als auch ohne ebCO-Layout kompiliert wurden, um Kunden zu unterstützen, die unterschiedliche Versionen des Compilers verwenden. Obwohl wir das Standardlayout nicht ändern können, können wir ein Mittel bereitstellen, um das Layout pro Klasse mit dem Hinzufügen des __declspec(empty_bases) Klassenattributes zu ändern. Eine mit diesem Attribut definierte Klasse kann EBCO vollständig nutzen.

struct __declspec(empty_bases) Derived3 : Empty2, Empty3
{
  char c;
};
static_assert(sizeof(Derived3) == 1, "Derived3 should be 1 byte"); // No Error
class Derived3  size(1):
   +---
0  | +--- (base class Empty2)
0  | | +--- (base class Empty1)
   | | +---
   | +---
0  | +--- (base class Empty3)
   | +---
0  | c
   +---

Alle Unterobjekte Derived3 werden bei Offset 0 angeordnet, und ihre Größe ist die optimale 1 Byte. Ein wichtiger Punkt zu beachten ist, dass __declspec(empty_bases) sich nur das Layout der Klasse auswirkt, auf die sie angewendet wird. Sie wird nicht rekursiv auf Basisklassen angewendet:

struct __declspec(empty_bases) Derived5 : Derived4
{
};
static_assert(sizeof(Derived5) == 4, "Derived5 should be 4 bytes"); // Error
class Derived5  size(8):
   +---
0  | +--- (base class Derived4)
0  | | +--- (base class Empty2)
0  | | | +--- (base class Empty1)
   | | | +---
   | | +---
1  | | +--- (base class Empty3)
   | | +---
   | | <alignment member> (size=3)
4  | | i
   | +---
   +---

Obwohl __declspec(empty_bases) sie angewendet Derived5wird, ist sie nicht für EBCO berechtigt, da sie keine direkten leeren Basisklassen aufweist, sodass sie keine Auswirkung hat. Wenn sie aber stattdessen auf die Derived4 Basisklasse angewendet wird, die für EBCO geeignet ist, haben beide ein Derived5Derived4 optimales Layout:

struct __declspec(empty_bases) Derived4 : Empty2, Empty3
{
  int i;
};
static_assert(sizeof(Derived4) == 4, "Derived4 should be 4 bytes"); // No Error

struct Derived5 : Derived4
{
};
static_assert(sizeof(Derived5) == 4, "Derived5 should be 4 bytes"); // No Error
class Derived5  size(4):
   +---
0  | +--- (base class Derived4)
0  | | +--- (base class Empty2)
0  | | | +--- (base class Empty1)
   | | | +---
   | | +---
0  | | +--- (base class Empty3)
   | | +---
0  | | i
   | +---
   +---

Aufgrund der Anforderung, dass sich alle Objektdateien und -bibliotheken auf das Klassenlayout einigen, __declspec(empty_bases) können nur auf von Ihnen gesteuerte Klassen angewendet werden. Sie kann nicht auf Klassen in der Standardbibliothek oder auf Klassen angewendet werden, die in Bibliotheken enthalten sind, die nicht auch mit dem EBCO-Layout neu kompiliert werden.

Ende Microsoft-spezifisch

Siehe auch

__declspec
Schlüsselwörter