Object
C++ Programme erstellen, zerstören, referenzieren, zugreifen auf und manipulieren Objekte.
Ein Objekt hat in C++
- Größe (ermittelbar mit
sizeof); - Ausrichtungsanforderung (ermittelbar mit
alignof); - Speicherdauer (automatisch, statisch, dynamisch, Thread-lokal);
- Lebensdauer (begrenzt durch Speicherdauer oder temporär);
- type;
- Wert (kann unbestimmt sein, z.B. für standardmäßig initialisierte Nicht-Klassen-Typen);
- optional einen Namen.
Die folgenden Entitäten sind keine Objekte: Wert, Referenz, Funktion, Aufzählungsmitglied, Typ, nicht-statisches Klassenmitglied, Template, Klassen- oder Funktionstemplate-Spezialisierung, Namensraum, Parameterpaket und this.
Ein Variable ist ein Objekt oder eine Referenz, die kein nicht-statisches Datenelement ist und durch eine Deklaration eingeführt wird.
Inhalt |
[bearbeiten] Objekterzeugung
Objekte können explizit durch Definitionen, new-Ausdrücken, throw-Ausdrücken, dem Ändern des aktiven Mitglieds einer union und der Auswertung von Ausdrücken, die temporäre Objekte erfordern, erstellt werden. Das erstellte Objekt ist bei expliziter Objekterzeugung eindeutig definiert.
Objekte von Implicit-Lifetime-Typen können auch implizit erstellt werden durch
- außerhalb der konstanten Auswertung, Operationen, die die Lebensdauer eines Arrays vom Typ unsigned char oder std::byte(seit C++17) beginnen, in diesem Fall werden solche Objekte im Array erstellt,
- Aufrufe folgender alloziierender Funktionen, in diesem Fall werden solche Objekte im allozierten Speicher erstellt
- operator new (außerhalb der konstanten Auswertung)
- operator new[] (außerhalb der konstanten Auswertung)
- std::malloc
- std::calloc
- std::realloc
| (seit C++17) |
- Aufrufe folgender Objektdarstellungs-Kopierfunktionen, in diesem Fall werden solche Objekte in der Zielregion des Speichers oder im Ergebnis erstellt
| (seit C++20) |
|
(seit C++23) |
Null oder mehr Objekte können im selben Speicherbereich erstellt werden, solange dies dem Programm ein definiertes Verhalten verleiht. Wenn eine solche Erzeugung unmöglich ist, z. B. aufgrund widersprüchlicher Operationen, ist das Verhalten des Programms undefiniert. Wenn mehrere solcher Sätze implizit erzeugter Objekte dem Programm ein definiertes Verhalten verleihen würden, ist nicht spezifiziert, welcher solche Satz von Objekten erzeugt wird. Mit anderen Worten, implizit erzeugte Objekte müssen nicht eindeutig definiert sein.
Nachdem Objekte innerhalb eines angegebenen Speicherbereichs implizit erstellt wurden, erzeugen einige Operationen einen Zeiger auf ein geeignetes erzeugtes Objekt. Das geeignete erzeugte Objekt hat dieselbe Adresse wie der Speicherbereich. Ebenso ist das Verhalten undefiniert, wenn kein solcher Zeigerwert dem Programm ein definiertes Verhalten verleiht, und es ist nicht spezifiziert, welcher Zeigerwert erzeugt wird, wenn mehrere Werte ein definiertes Verhalten verleihen.
#include <cstdlib> struct X { int a, b; }; X* MakeX() { // One of possible defined behaviors: // the call to std::malloc implicitly creates an object of type X // and its subobjects a and b, and returns a pointer to that X object X* p = static_cast<X*>(std::malloc(sizeof(X))); p->a = 1; p->b = 2; return p; }
Aufrufe von std::allocator::allocate oder implizit definierte Kopier-/Verschiebe-Spezialmemberfunktionen von union-Typen können ebenfalls Objekte erstellen.
[bearbeiten] Objektdarstellung und Wertdarstellung
Einige Typen und Objekte haben Objektdarstellungen und Wertdarstellungen, die in der folgenden Tabelle definiert sind
| Entität | Objektdarstellung | Wertdarstellung |
|---|---|---|
ein vollständiger Objekttyp T |
die Sequenz von N unsigned char Objekten, die von einem vollständigen Objekt vom Typ T ohne Bitfeld belegt werden, wobei N gleich sizeof(T) ist |
die Menge von Bits in der Objektdarstellung von T, die an der Darstellung eines Werts vom Typ T beteiligt sind |
ein vollständiges Objekt obj ohne Bitfeld vom Typ T |
die Bytes von obj, die der Objektdarstellung von T entsprechen |
die Bits von obj, die der Wertdarstellung von T entsprechen |
| ein Bitfeldobjekt bf | die Sequenz von N Bits, die von bf belegt werden, wobei N die Breite des Bitfelds ist | die Menge von Bits in der Objektdarstellung von bf, die an der Darstellung des Werts von bf beteiligt sind |
Bits in der Objektdarstellung eines Typs oder Objekts, die nicht Teil der Wertdarstellung sind, sind Füllbits.
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 anderes Objekt mit demselben Wert zu erzeugen (außer wenn das Objekt ein potenziell überlappendes Unterobjekt ist oder der Wert eine Trap-Darstellung seines Typs ist und das Laden in die CPU eine Hardware-Ausnahme auslöst, wie z. B. SNaN ("signalling not-a-number") Gleitkommawerte oder NaT ("not-a-thing") Ganzzahlwerte).
Obwohl die meisten Implementierungen keine Trap-Darstellungen, Füllbits oder mehrere Darstellungen für Ganzzahltypen zulassen, gibt es Ausnahmen; z.B. kann ein Wert eines Ganzzahltyps auf Itanium eine Trap-Darstellung sein.
Das Umgekehrte ist nicht unbedingt der Fall: Zwei Objekte eines TriviallyCopyable-Typs mit unterschiedlichen Objektdarstellungen können denselben Wert darstellen. Zum Beispiel repräsentieren mehrere Gleitkomma-Bitmuster denselben Spezialwert NaN. Häufiger können Füllbits eingefügt werden, um Ausrichtungsanforderungen, Bitfeld-Größen usw. zu erfüllen.
#include <cassert> struct S { char c; // 1 byte value // 3 bytes of padding bits (assuming alignof(float) == 4) float f; // 4 bytes value (assuming sizeof(float) == 4) bool operator==(const S& arg) const // value-based equality { 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'; // modify some padding bits assert(s1 == s2); // value did not change }
Für Objekte vom Typ char, signed char und unsigned char (sofern es sich nicht um übergroße Bitfelder handelt) müssen alle Bits der Objektdarstellung an der Wertdarstellung beteiligt sein und jedes mögliche Bitmuster repräsentiert einen einzelnen Wert (keine Füllbits, Trap-Bits oder Mehrfachdarstellungen zulässig).
[bearbeiten] Unterobjekte
Ein Objekt kann Unterobjekte haben. Dazu gehören
- Mitgliedsobjekte
- Basisklassen-Unterobjekte
- 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 von Klassentyp ist, gilt sein Typ als die am meisten abgeleitete Klasse, um ihn vom Klassentyp eines Basisklassen-Unterobjekts zu unterscheiden. Ein Objekt eines am meisten abgeleiteten Klassentyps oder eines Nicht-Klassentyps wird als am meisten abgeleitetes Objekt bezeichnet.
Für eine Klasse,
- ihre nicht-statischen Datenelemente,
- ihre nicht-virtuellen direkten Basisklassen und,
- wenn die Klasse nicht abstrakt ist, ihre virtuellen Basisklassen
werden als ihre potenziell zu konstruierenden Unterobjekte bezeichnet.
[bearbeiten] Größe
Ein Unterobjekt ist ein potenziell überlappendes Unterobjekt, wenn es ein Basisklassen-Unterobjekt ist oder ein nicht-statisches Datenelement, das mit dem Attribut [[no_unique_address]] deklariert ist(seit C++20).
Ein Objekt obj kann nur eine Größe von Null haben, wenn alle folgenden Bedingungen erfüllt sind
- obj ist ein potenziell überlappendes Unterobjekt.
- obj ist vom Typ einer Klasse ohne virtuelle Memberfunktionen und virtuelle Basisklassen.
- obj hat keine Unterobjekte 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-Unterobjekt eines standard-layout(seit C++11) Klassentyps ohne nicht-statische Datenelemente ist, hat es die Größe Null.
- Andernfalls ist es implementierungsabhängig, unter welchen Umständen obj die Größe Null hat.
Siehe Optimierung für leere Basisklassen für weitere Details.
Jedes Nicht-Bitfeld-Objekt mit einer Größe ungleich Null muss einen oder mehrere Bytes Speicherplatz belegen, einschließlich jedes Bytes, das (ganz oder teilweise) von einem seiner Unterobjekte belegt wird. Der belegte Speicher muss zusammenhängend sein, wenn das Objekt von einem trivially copyable oder standard-layout(seit C++11) Typ ist.
[bearbeiten] Adresse
Sofern ein Objekt kein Bitfeld oder ein Unterobjekt der Größe Null 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 in das erstere Objekt verschachtelt. Ein Objekt a ist in einem anderen Objekt b verschachtelt, wenn eine der folgenden Bedingungen erfüllt ist
- a ist ein Unterobjekt von b.
- b stellt Speicher für a bereit.
- Es existiert ein Objekt c, wobei a in c verschachtelt ist und c in b verschachtelt ist.
Ein Objekt ist ein potenziell nicht eindeutiges Objekt, wenn es eines der folgenden Objekte ist
- Ein Objekt eines String-Literals.
|
(seit C++11) |
- Ein Unterobjekt eines potenziell nicht eindeutigen Objekts.
Für zwei Nicht-Bitfeld-Objekte mit überlappenden Lebensdauern
- Wenn eine der folgenden Bedingungen erfüllt ist, können sie dieselbe Adresse haben
- Eines von ihnen ist in das andere verschachtelt.
- Eines von ihnen ist ein Unterobjekt der Größe Null und ihre Typen sind nicht ähnlich.
- Sie sind beide potenziell nicht eindeutige Objekte.
- Andernfalls haben sie immer unterschiedliche Adressen und belegen getrennte Bytes im Speicher.
// character literals are always unique static const char test1 = 'x'; static const char test2 = 'x'; const bool b = &test1 != &test2; // always true // the character 'x' accessed from “r”, “s” and “il” // may have the same address (i.e., these objects may share storage) static const char (&r) [] = "x"; static const char *s = "x"; static std::initializer_list<char> il = {'x'}; const bool b2 = r != il.begin(); // unspecified result const bool b3 = r != s; // unspecified result const bool b4 = il.begin() != &test1; // always true const bool b5 = r != &test1; // always true
[bearbeiten] 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 (bei jeder existierenden Implementierung ist dies ein Zeiger, es sei denn, er wird optimiert), die für virtuelle Funktionsaufrufe und für die RTTI-Funktionen (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 aus dem Ausdruck bestimmt, in dem das Objekt verwendet wird, und zur Kompilierzeit festgelegt.
#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
[bearbeiten] Strikte Aliasing-Regel
Der Zugriff auf ein Objekt über einen Ausdruck eines anderen Typs als des Typs, mit dem es erstellt wurde, führt in vielen Fällen zu undefiniertem Verhalten; siehe reinterpret_cast für die Liste der Ausnahmen und Beispiele.
[bearbeiten] Ausrichtung
Jeder Objekttyp hat die Eigenschaft Ausrichtungsanforderung, die ein nicht-negativer ganzzahliger Wert ist (vom Typ std::size_t und immer eine Zweierpotenz), der die Anzahl der Bytes zwischen aufeinanderfolgenden Adressen repräsentiert, an denen Objekte dieses Typs alloziert werden können.
|
Die Ausrichtungsanforderung eines Typs kann mit |
(seit C++11) |
Jeder Objekttyp erzwingt seine Ausrichtungsanforderung an jedes Objekt dieses Typs; eine strengere Ausrichtung (mit größerer Ausrichtungsanforderung) kann mit alignas angefordert werden(seit C++11). Der Versuch, ein Objekt in Speicher zu erstellen, der die Ausrichtungsanforderungen des Objekttyps nicht erfüllt, führt zu undefiniertem Verhalten.
Um die Ausrichtungsanforderungen aller nicht-statischen Member einer Klasse zu erfüllen, können Füllbits nach einigen ihrer Member 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 jedes Typs ist implementierungsabhängig 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 strenger (größer) als std::max_align_t mittels Allocator-Typen müssen über-ausgerichtete Typen korrekt behandeln. |
(seit C++11) |
|
Es ist implementierungsabhängig, ob new-Ausdrücke und(bis C++17) std::get_temporary_buffer über-ausgerichtete Typen unterstützen. |
(seit C++11) (bis C++20) |
[bearbeiten] Anmerkungen
Objekte in C++ haben eine andere Bedeutung als Objekte in der objektorientierten 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 der "Instanz" (und es gibt Mechanismen wie instanceof, um Beziehungen vom Typ "Instanz-von" zu erkennen) |
| kein Konzept von "Schnittstelle" | haben das Konzept der "Schnittstelle" (und es gibt Mechanismen wie instanceof, um zu erkennen, ob eine Schnittstelle implementiert ist) |
| Polymorphismus muss explizit über virtuelle Member aktiviert werden | Polymorphismus ist immer aktiviert |
Im Fehlerbericht P0593R6 wurde die implizite Objekterzeugung als stattfindend betrachtet, wenn ein Byte-Array erstellt oder eine Allokationsfunktion aufgerufen wird (die möglicherweise benutzerdefiniert und constexpr ist) während der konstanten Auswertung. Solche Zulassungen führten jedoch zu Indeterminismus bei der konstanten Auswertung, was unerwünscht und in gewisser Hinsicht nicht implementierbar war. Daher P2747R2 verbietet eine solche implizite Objekterzeugung in der konstanten Auswertung. Wir behandeln eine solche Änderung als Fehlerbericht, obwohl das gesamte Papier dies nicht tut.
[bearbeiten] 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 definierte Variablen im selben Gültigkeitsbereich, die garantiert denselben Wert haben, dieselbe Adresse haben können |
Adresse ist garantiert unterschiedlich, wenn ihre Lebensdauern sich überlappen, unabhängig von ihren Werten |
| CWG 1189 | C++98 | zwei Basisklassen-Unterobjekte desselben Typs dieselbe Adresse haben könnten |
sie haben immer unterschiedliche Adressen |
| CWG 1861 | C++98 | bei übergroßen Bitfeldern von schmalen Zeichen- Typen, alle Bits der Objektdarstellung beteiligten sich immer noch an der Wertdarstellung |
erlaubt Füllbits |
| CWG 2489 | C++98 | char[] können keinen Speicher bereitstellen, aber Objekte könnten implizit in seinem Speicher erstellt werden |
Objekte können nicht implizit erstellt werden innerhalb des Speichers von char[] |
| CWG 2519 | C++98 | die Definition der Objektdarstellung bezog sich nicht auf Bitfelder | bezieht sich auf Bitfelder |
| CWG 2719 | C++98 | das Verhalten beim Erstellen eines Objekts in nicht ausgerichteten Speicher war unklar |
Das Verhalten ist in diesem Fall nicht definiert. |
| CWG 2753 | C++11 | es war unklar, ob ein Basisspeicherarray eines Initialisierungsliste Speicher mit einem String-Literal teilen kann |
sie können Speicher teilen |
| CWG 2795 | C++98 | bei der Bestimmung, ob zwei Objekte mit überlappenden Lebensdauern dieselbe Adresse haben können, wenn eines von ihnen ein Unterobjekt 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, die von der Standardbibliothek benötigt wurden und war nicht kompatibel mit effektiven Typen in C |
implizite Objekterzeugung hinzugefügt |
[bearbeiten] Siehe auch
| C-Dokumentation für Objekt
|