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.
Inhalt
ObjekterstellungObjekte 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
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 WertedarstellungEinige Typen und Objekte haben Objektdarstellungen und Wertdarstellungen , sie sind in der folgenden Tabelle definiert:
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). SubobjekteEin Objekt kann Subobjekte besitzen. Diese umfassen
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,
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
Ein Objekt obj kann nur dann möglicherweise eine Größe von Null haben, wenn alle folgenden Bedingungen erfüllt sind:
Für ein Objekt obj das alle oben genannten Bedingungen erfüllt:
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. AdresseSofern 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:
Ein Objekt ist ein potenziell nicht eindeutiges Objekt , wenn es eines der folgenden Objekte ist:
Für zwei beliebige Nicht-Bitfeld-Objekte mit überlappenden Lebensdauern :
// 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 (
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.
Diesen Code ausführen
#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
AusrichtungJeder Objekttyp hat die Eigenschaft namens Ausrichtungsanforderung, ein nichtnegativer Ganzzahlwert (vom Typ std::size_t, stets eine Zweierpotenz), der die Anzahl der Bytes zwischen aufeinanderfolgenden Adressen darstellt, an denen Objekte dieses Typs zugewiesen werden können.
Jeder Objekttyp erlegt seine Ausrichtungsanforderung jedem Objekt dieses Typs auf; eine strengere Ausrichtung (mit größerer Ausrichtungsanforderung) kann mit Um die Ausrichtungsanforderungen aller nicht-statischen Elemente einer Klasse zu erfüllen, können Padding-Bits nach einigen ihrer Elemente eingefügt werden. Diesen Code ausführen #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 Wenn eine Anforderung für eine bestimmte erweiterte Ausrichtung in einem bestimmten Kontext von einer Implementierung nicht unterstützt wird, ist das Programm fehlerhaft.
HinweiseObjekte in C++ haben eine andere Bedeutung als Objekte in objektorientierter Programmierung (OOP) :
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
FehlerberichteDie folgenden verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
Siehe auch
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||