Object
C++-Programme erstellen, zerstören, referenzieren, greifen zu und manipulieren Objekte .
Ein Objekt, in C++, hat
-
Größe (kann mit
sizeofbestimmt werden); -
Ausrichtungsanforderung (kann mit
alignofbestimmt werden); - Speicherdauer (automatisch, statisch, dynamisch, thread-lokal);
- Lebensdauer (begrenzt durch Speicherdauer oder temporär);
- Typ ;
- Wert (der unbestimmt sein kann, z.B. für standardinitialisierte Nicht-Klassen-Typen);
- optional, ein Name .
Die folgenden Entitäten sind keine Objekte: Wert, Referenz, Funktion, Enumerator, Typ, nicht-statisches Klassenmitglied, Template, Klassen- oder Funktions-Template-Spezialisierung, Namensbereich, Parameterpaket und this .
Eine Variable ist ein Objekt oder eine Referenz, die kein nicht-statisches Datenelement ist und durch eine Deklaration eingeführt wird.
Inhaltsverzeichnis |
Objekterstellung
Objekte können explizit durch Definitionen , new -Ausdrücke , throw -Ausdrücke , das Ändern des aktiven Members eines Unions und die Auswertung von Ausdrücken, die temporäre Objekte erfordern, erstellt werden. Das erstellte Objekt ist bei der expliziten Objekterstellung eindeutig definiert.
Objekte von implicit-lifetime types können auch implizit erstellt werden durch
- außer während der Konstantenauswertung, Operationen, die die Lebensdauer eines Arrays vom Typ unsigned char oder std::byte (since C++17) , in welchem Fall solche Objekte im Array erzeugt werden,
- Aufruf der folgenden allozierenden Funktionen, in welchem Fall solche Objekte im allokierten Speicher erzeugt werden:
-
- operator new (außer während konstanter Auswertung)
- operator new[] (außer während konstanter Auswertung)
- std::malloc
- std::calloc
- std::realloc
| (seit C++17) |
- Aufruf der folgenden Objektrepräsentation Kopierfunktionen, wobei solche Objekte im Zielspeicherbereich oder dem Ergebnis erstellt werden:
| (seit C++20) |
|
(since C++23) |
Null oder mehr Objekte können in derselben Speicherregion erzeugt werden, solange dies dem Programm definiertes Verhalten verleiht. Falls eine solche Erzeugung unmöglich ist, z.B. aufgrund konfligierender Operationen, ist das Verhalten des Programms undefiniert. Falls mehrere solcher Sätze implizit erzeugter Objekte dem Programm definiertes Verhalten verleihen würden, ist nicht spezifiziert, welcher dieser Objektsätze erzeugt wird. Mit anderen Worten: implizit erzeugte Objekte müssen nicht eindeutig definiert sein.
Nach dem impliziten Erstellen von Objekten innerhalb eines bestimmten Speicherbereichs erzeugen einige Operationen einen Zeiger auf ein suitable created object . Das suitable created object hat dieselbe Adresse wie der Speicherbereich. Ebenso ist das Verhalten nur dann undefiniert, wenn kein solcher Zeigerwert dem Programm definiertes Verhalten geben kann, und es ist nicht spezifiziert, welcher Zeigerwert erzeugt wird, wenn mehrere Werte dem Programm definiertes Verhalten geben.
#include <cstdlib> struct X { int a, b; }; X* MakeX() { // Eines der möglichen definierten Verhaltensweisen: // Der Aufruf von std::malloc erstellt implizit ein Objekt vom Typ X // und seine Unterobjekte a und b, und gibt einen Zeiger auf dieses X-Objekt zurück X* p = static_cast<X*>(std::malloc(sizeof(X))); p->a = 1; p->b = 2; return p; }
Aufruf von std::allocator::allocate oder implizit definierte Kopier-/Verschiebe-Spezialmemberfunktionen von union -Typen können ebenfalls Objekte erzeugen.
Objektdarstellung und Wertedarstellung
Einige Typen und Objekte haben Objektdarstellungen und Wertdarstellungen , sie sind in der folgenden Tabelle definiert:
| Entität | Objektdarstellung | Wertdarstellung |
|---|---|---|
ein vollständiger Objekttyp
T
|
die Folge von
N
unsigned
char
Objekten, die von einem
Bitfeld
-freien vollständigen Objekt des Typs
T
belegt werden, wobei
N
gleich
sizeof
(
T
)
ist
|
die Menge der Bits in der Objektdarstellung von
T
, die an der Darstellung eines Werts des Typs
T
teilnehmen
|
ein Bitfeld-freies vollständiges Objekt
obj
des Typs
T
|
die Bytes von
obj
, die der Objektdarstellung von
T
entsprechen
|
die Bits von
obj
, die der Wertdarstellung von
T
entsprechen
|
| ein Bitfeld-Objekt bf | die Folge von N Bits, die von bf belegt werden, wobei N die Breite des Bitfelds ist | die Menge der Bits in der Objektdarstellung von bf , die an der Darstellung des Werts von bf teilnehmen |
Bits in der Objektdarstellung eines Typs oder Objekts, die nicht Teil der Wertedarstellung sind, werden Padding-Bits genannt.
Für TriviallyCopyable -Typen ist die Wertdarstellung Teil der Objektdarstellung, was bedeutet, dass das Kopieren der vom Objekt im Speicher belegten Bytes ausreicht, um ein weiteres Objekt mit demselben Wert zu erzeugen (außer wenn das Objekt ein potenziell überlappendes Subobjekt ist oder der Wert eine Trap-Darstellung seines Typs ist und das Laden in die CPU eine Hardware-Ausnahme auslöst, wie etwa SNaN ("signalling not-a-number") Fließkommawerte oder NaT ("not-a-thing") Ganzzahlen).
Obwohl die meisten Implementierungen keine Trap-Darstellungen, Padding-Bits oder Mehrfachdarstellungen für Integer-Typen erlauben, gibt es Ausnahmen; zum Beispiel kann ein Wert eines Integer-Typs auf Itanium eine Trap-Darstellung sein .
Das Umgekehrte gilt nicht notwendigerweise: Zwei Objekte eines TriviallyCopyable -Typs mit unterschiedlichen Objektrepräsentationen können denselben Wert repräsentieren. Beispielsweise repräsentieren mehrere Gleitkomma-Bitmuster denselben speziellen Wert NaN . Häufiger können Füllbits eingefügt werden, um Ausrichtungsanforderungen , Bitfeld -Größen etc. zu erfüllen.
#include <cassert> struct S { char c; // 1-Byte-Wert // 3 Bytes Füllbits (angenommen alignof(float) == 4) float f; // 4-Byte-Wert (angenommen sizeof(float) == 4) bool operator==(const S& arg) const // wertbasierte Gleichheit { return c == arg.c && f == arg.f; } }; void f() { assert(sizeof(S) == 8); S s1 = {'a', 3.14}; S s2 = s1; reinterpret_cast<unsigned char*>(&s1)[2] = 'b'; // einige Füllbits modifizieren assert(s1 == s2); // Wert hat sich nicht geändert }
Für Objekte vom Typ char , signed char und unsigned char (sofern es sich nicht um übergroße Bitfelder handelt) muss jedes Bit der Objektdarstellung an der Wertdarstellung teilnehmen und jedes mögliche Bitmuster repräsentiert einen eindeutigen Wert (keine Padding-Bits, Trap-Bits oder Mehrfachdarstellungen erlaubt).
Subobjekte
Ein Objekt kann Subobjekte besitzen. Diese umfassen
- Member-Objekte
- Basisklassen-Subobjekte
- Array-Elemente
Ein Objekt, das kein Unterobjekt eines anderen Objekts ist, wird als vollständiges Objekt bezeichnet.
Wenn ein vollständiges Objekt, ein Mitgliedsunterobjekt oder ein Array-Element vom Klassentyp ist, wird sein Typ als am stärksten abgeleitete Klasse betrachtet, um ihn vom Klassentyp eines Basisklassenunterobjekts zu unterscheiden. Ein Objekt vom Typ einer am stärksten abgeleiteten Klasse oder eines Nicht-Klassentyps wird als am stärksten abgeleitetes Objekt bezeichnet.
Für eine Klasse,
- seine nicht-statischen Datenmember ,
- seine nicht-virtuellen direkten Basisklassen , und,
- falls die Klasse nicht abstrakt ist, seine virtuellen Basisklassen
werden als seine potenziell konstruierten Unterobjekte bezeichnet.
Größe
Ein Unterobjekt ist ein
potenziell überlappendes Unterobjekt
, wenn es sich um ein Basisklassen-Unterobjekt handelt
oder um ein nicht-statisches Datenelement, das mit dem
[[
no_unique_address
]]
-Attribut deklariert wurde
(seit C++20)
.
Ein Objekt obj kann nur dann möglicherweise eine Größe von Null haben, wenn alle folgenden Bedingungen erfüllt sind:
- obj ist ein potenziell überlappendes Subobjekt.
- obj ist von einem Klassentyp ohne virtuelle Memberfunktionen und virtuelle Basisklassen.
- obj besitzt kein Subobjekt mit nicht-null Größe oder unbenannte Bitfelder mit nicht-null Länge.
Für ein Objekt obj das alle oben genannten Bedingungen erfüllt:
- Wenn obj ein Basisklassen-Subobjekt eines Standard-Layout (seit C++11) Klassentyps ohne nicht-statische Datenelemente ist, hat es Nullgröße.
- Andernfalls ist implementierungsdefiniert, unter welchen Umständen obj Nullgröße hat.
Siehe empty base optimization für weitere Details.
Jedes Nicht-Bitfeld-Objekt mit einer Größe ungleich Null muss einen oder mehrere Bytes an Speicher belegen, einschließlich jedes Bytes, das (vollständig oder teilweise) von einem seiner Unterobjekte belegt wird. Der belegte Speicher muss zusammenhängend sein, wenn das Objekt vom trivial kopierbaren oder Standard-Layout (since C++11) Typ ist.
Adresse
Sofern ein Objekt kein Bitfeld oder ein Unterobjekt von Nullgröße ist, ist die Adresse dieses Objekts die Adresse des ersten Bytes , das es belegt.
Ein Objekt kann andere Objekte enthalten, in diesem Fall sind die enthaltenen Objekte innerhalb des ersteren Objekts geschachtelt. Ein Objekt a ist innerhalb eines anderen Objekts b geschachtelt, wenn eine der folgenden Bedingungen erfüllt ist:
- a ist ein Unterobjekt von b .
- b stellt Speicher bereit für a .
- Es existiert ein Objekt c , in dem a innerhalb von c geschachtelt ist und c innerhalb von b geschachtelt ist.
Ein Objekt ist ein potenziell nicht eindeutiges Objekt , wenn es eines der folgenden Objekte ist:
- Ein String-Literal Objekt.
|
(seit C++11) |
- Ein Unterobjekt eines potenziell nicht eindeutigen Objekts.
Für zwei beliebige Nicht-Bitfeld-Objekte mit überlappenden Lebensdauern :
- Wenn eine der folgenden Bedingungen erfüllt ist, können sie dieselbe Adresse haben:
-
- Einer von ihnen ist innerhalb des anderen verschachtelt.
- Einer von ihnen ist ein Unterobjekt mit Nullgröße, und ihre Typen sind nicht ähnlich .
- Sie sind beide potenziell nicht-eindeutige Objekte.
- Andernfalls haben sie stets unterschiedliche Adressen und belegen disjunkte Bytes des Speichers.
// Zeichenliterale sind immer eindeutig static const char test1 = 'x'; static const char test2 = 'x'; const bool b = &test1 != &test2; // immer true // Das Zeichen 'x' aus "r", "s" und "il" // kann dieselbe Adresse haben (d.h. diese Objekte können Speicher teilen) static const char (&r) [] = "x"; static const char *s = "x"; static std::initializer_list<char> il = {'x'}; const bool b2 = r != il.begin(); // nicht spezifiziertes Ergebnis const bool b3 = r != s; // nicht spezifiziertes Ergebnis const bool b4 = il.begin() != &test1; // immer true const bool b5 = r != &test1; // immer true
Polymorphe Objekte
Objekte eines Klassentyps, die mindestens eine virtuelle Funktion deklarieren oder erben, sind polymorphe Objekte. Innerhalb jedes polymorphen Objekts speichert die Implementierung zusätzliche Informationen (in jeder existierenden Implementierung ist dies ein Zeiger, sofern nicht wegoptimiert), die von
virtuellen Funktionsaufrufen
und den RTTI-Features (
dynamic_cast
und
typeid
) verwendet werden, um zur Laufzeit den Typ zu bestimmen, mit dem das Objekt erstellt wurde, unabhängig vom Ausdruck, in dem es verwendet wird.
Für nicht-polymorphe Objekte wird die Interpretation des Werts durch den Ausdruck bestimmt, in dem das Objekt verwendet wird, und wird zur Kompilierzeit entschieden.
#include <iostream> #include <typeinfo> struct Base1 { // polymorphic type: declares a virtual member virtual ~Base1() {} }; struct Derived1 : Base1 { // polymorphic type: inherits a virtual member }; struct Base2 { // non-polymorphic type }; struct Derived2 : Base2 { // non-polymorphic type }; int main() { Derived1 obj1; // object1 created with type Derived1 Derived2 obj2; // object2 created with type Derived2 Base1& b1 = obj1; // b1 refers to the object obj1 Base2& b2 = obj2; // b2 refers to the object obj2 std::cout << "Expression type of b1: " << typeid(decltype(b1)).name() << '\n' << "Expression type of b2: " << typeid(decltype(b2)).name() << '\n' << "Object type of b1: " << typeid(b1).name() << '\n' << "Object type of b2: " << typeid(b2).name() << '\n' << "Size of b1: " << sizeof b1 << '\n' << "Size of b2: " << sizeof b2 << '\n'; }
Mögliche Ausgabe:
Expression type of b1: Base1 Expression type of b2: Base2 Object type of b1: Derived1 Object type of b2: Base2 Size of b1: 8 Size of b2: 1
Strict Aliasing
Das Zugreifen auf ein Objekt mit einem Ausdruck eines anderen Typs als dem, mit dem es erstellt wurde, ist in vielen Fällen undefiniertes Verhalten. Siehe
reinterpret_cast
für die Liste der Ausnahmen und Beispiele.
Ausrichtung
Jeder Objekttyp besitzt eine Eigenschaft namens Ausrichtungsanforderung , die einen nichtnegativen ganzzahligen Wert (vom Typ std::size_t , stets eine Zweierpotenz) darstellt und die Anzahl Bytes zwischen aufeinanderfolgenden Adressen angibt, an denen Objekte dieses Typs allokiert werden können.
|
Die Ausrichtungsanforderung eines Typs kann mit
|
(seit C++11) |
Jeder Objekttyp stellt seine Ausrichtungsanforderung an jedes Objekt dieses Typs
; strengere Ausrichtung (mit größerer Ausrichtungsanforderung) kann mittels
alignas
angefordert werden
(seit C++11)
. Der Versuch, ein Objekt in Speicher zu erstellen, der nicht den Ausrichtungsanforderungen des Objekttyps entspricht, ist undefiniertes Verhalten.
Um die Ausrichtungsanforderungen aller nicht-statischen Mitglieder einer Klasse zu erfüllen, können Padding-Bits nach einigen ihrer Mitglieder eingefügt werden.
#include <iostream> // objects of type S can be allocated at any address // because both S.a and S.b can be allocated at any address struct S { char a; // size: 1, alignment: 1 char b; // size: 1, alignment: 1 }; // size: 2, alignment: 1 // objects of type X must be allocated at 4-byte boundaries // because X.n must be allocated at 4-byte boundaries // because int's alignment requirement is (usually) 4 struct X { int n; // size: 4, alignment: 4 char c; // size: 1, alignment: 1 // three bytes of padding bits }; // size: 8, alignment: 4 int main() { std::cout << "alignof(S) = " << alignof(S) << '\n' << "sizeof(S) = " << sizeof(S) << '\n' << "alignof(X) = " << alignof(X) << '\n' << "sizeof(X) = " << sizeof(X) << '\n'; }
Mögliche Ausgabe:
alignof(S) = 1 sizeof(S) = 2 alignof(X) = 4 sizeof(X) = 8
Die schwächste Ausrichtung (die kleinste Ausrichtungsanforderung) ist die Ausrichtung von char , signed char und unsigned char , die gleich 1 ist; die größte fundamentale Ausrichtung eines beliebigen Typs ist implementierungsdefiniert und gleich der Ausrichtung von std::max_align_t (seit C++11) .
Fundamentale Ausrichtungen werden für Objekte aller Arten von Speicherdauern unterstützt.
|
Wenn die Ausrichtung eines Typs mithilfe von
Allocator -Typen müssen überausgerichtete Typen korrekt verarbeiten. |
(since C++11) |
|
Es ist implementierungsdefiniert, ob new -Ausdrücke und (bis C++17) std::get_temporary_buffer überalignierte Typen unterstützen. |
(seit C++11)
(bis C++20) |
Hinweise
Objekte in C++ haben eine andere Bedeutung als Objekte in objektorientierter Programmierung (OOP) :
| Objekte in C++ | Objekte in OOP |
|---|---|
|
können jeden Objekttyp haben
(siehe std::is_object ) |
müssen einen Klassentyp haben |
| kein Konzept von "Instanz" |
haben das Konzept von "Instanz" (und es gibt Mechanismen wie
instanceof
um "Instanz-von"-Beziehungen zu erkennen)
|
| kein Konzept von "Interface" |
haben das Konzept von "Interface" (und es gibt Mechanismen wie
instanceof
um zu erkennen, ob ein Interface implementiert ist)
|
| Polymorphie muss explizit durch virtuelle Member aktiviert werden | Polymorphie ist immer aktiviert |
Im Defektbericht
P0593R6
wurde die implizite Objekterstellung ursprünglich als stattfindend betrachtet, wenn ein Byte-Array erstellt oder eine
Allokationsfunktion
aufgerufen wird (die möglicherweise benutzerdefiniert und
constexpr
ist). Jedoch verursachte diese Erlaubnis Nichtdeterminismus in der Konstantenauswertung, was unerwünscht und in manchen Aspekten nicht implementierbar war. Infolgedessen hat
P2747R2
solche implizite Objekterstellung in der Konstantenauswertung untersagt. Wir behandeln diese Änderung bewusst als Defektbericht, obwohl das gesamte Dokument keiner ist.
Fehlerberichte
Die folgenden verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
| DR | Angewendet auf | Verhalten wie veröffentlicht | Korrigiertes Verhalten |
|---|---|---|---|
| CWG 633 | C++98 | Variablen konnten nur Objekte sein | sie können auch Referenzen sein |
| CWG 734 | C++98 |
es war nicht spezifiziert, ob Variablen im selben
Gültigkeitsbereich, die garantiert denselben Wert haben, dieselbe Adresse haben können |
Adresse ist garantiert unterschiedlich, wenn sich
ihre Lebensdauern überschneiden, unabhängig von ihren Werten |
| CWG 1189 | C++98 |
zwei Basisklassen-Subobjekte desselben Typs
könnten dieselbe Adresse haben |
sie haben immer
unterschiedliche Adressen |
| CWG 1861 | C++98 |
für überbreite Bitfelder schmaler Zeichentypen
nahmen alle Bits der Objektdarstellung noch an der Wertdarstellung teil |
erlaubt Padding-Bits |
| CWG 2489 | C++98 |
char
[
]
kann keinen Speicher bereitstellen, aber Objekte
konnten implizit in seinem Speicher erstellt werden |
Objekte können nicht implizit im Speicher
von char [ ] erstellt werden |
| CWG 2519 | C++98 | die Definition der Objektdarstellung behandelte Bitfelder nicht | behandelt Bitfelder |
| CWG 2719 | C++98 |
das Verhalten bei der Erstellung eines Objekts
in nicht ausgerichtetem Speicher war unklar |
das Verhalten ist in diesem Fall
undefiniert |
| CWG 2753 | C++11 |
es war unklar, ob ein Backing-Array eines
Initialisierer-Liste Speicher mit einem String-Literal teilen kann |
sie können Speicher teilen |
| CWG 2795 | C++98 |
bei der Bestimmung, ob zwei Objekte mit sich überschneidenden
Lebensdauern dieselbe Adresse haben können, wenn eines davon ein Subobjekt der Größe Null ist, könnten sie ähnliche unterschiedliche Typen haben |
erlaubt nur nicht-ähnliche Typen |
| P0593R6 | C++98 |
das vorherige Objektmodell unterstützte viele nützliche
Idiome nicht, die von der Standardbibliothek benötigt werden, und war nicht kompatibel mit effektiven Typen in C |
implizite Objekterstellung hinzugefügt |
Siehe auch
|
C-Dokumentation
für
Object
|