Union-Deklaration
Eine Union ist ein spezieller Klassentyp, der zu einem bestimmten Zeitpunkt nur eines seiner nicht-statischen Datenmember enthalten kann.
Inhalt |
[bearbeiten] Syntax
Der Klassenspezifizierer für eine Union-Deklaration ähnelt der Klassen- oder Strukturdeklaration.
union attr class-head-name { member-specification } |
|||||||||
| attr | - | (seit C++11) optionale Sequenz beliebig vieler Attribute |
| class-head-name | - | Der Name der zu definierenden Union. Optional vorangestellt durch nested-name-specifier (Sequenz von Namen und Bereichsauflösungsoperatoren, endend mit einem Bereichsauflösungsoperator). Der Name kann weggelassen werden, in diesem Fall ist die Union *unbenannt*. |
| member-specification | - | Liste von Zugriffsmodifizierern, Memberobjekt- und Memberfunktionsdeklarationen und -definitionen. |
Eine Union kann Memberfunktionen haben (einschließlich Konstruktoren und Destruktoren), jedoch keine virtuellen Funktionen.
Eine Union kann keine Basisklassen haben und nicht als Basisklasse verwendet werden.
|
Höchstens ein Variantenmember kann einen Standardmember-Initialisierer haben. |
(seit C++11) |
Eine Union kann keine nicht-statischen Datenmember vom Referenztyp haben.
|
Unions können keinen nicht-statischen Datenmember mit einer nicht-trivialen speziellen Memberfunktion enthalten. |
(bis C++11) |
|
Wenn eine Union einen nicht-statischen Datenmember mit einer nicht-trivialen speziellen Memberfunktion enthält, kann die entsprechende spezielle Memberfunktion der Union als gelöscht definiert werden. Details finden Sie auf der entsprechenden Seite zur speziellen Memberfunktion. |
(seit C++11) |
Genau wie bei der Strukturdeklaration ist der Standard-Memberzugriff in einer Union public.
[bearbeiten] Erklärung
Die Union ist mindestens so groß wie nötig, um ihren größten Datenmember aufzunehmen, ist aber normalerweise nicht größer. Die anderen Datenmember sollen im selben Speicherbereich als Teil dieses größten Members zugewiesen werden. Die Details dieser Zuweisung sind implementierungsabhängig, mit der Ausnahme, dass alle nicht-statischen Datenmember die gleiche Adresse haben. Das Lesen aus dem Member der Union, der nicht zuletzt geschrieben wurde, ist undefiniertes Verhalten. Viele Compiler implementieren als nicht-standardmäßige Spracherweiterung die Möglichkeit, inaktive Member einer Union zu lesen.
#include <cstdint> #include <iostream> union S { std::int32_t n; // occupies 4 bytes std::uint16_t s[2]; // occupies 4 bytes std::uint8_t c; // occupies 1 byte }; // the whole union occupies 4 bytes int main() { S s = {0x12345678}; // initializes the first member, s.n is now the active member // At this point, reading from s.s or s.c is undefined behavior, // but most compilers define it. std::cout << std::hex << "s.n = " << s.n << '\n'; s.s[0] = 0x0011; // s.s is now the active member // At this point, reading from s.n or s.c is undefined behavior, // but most compilers define it. std::cout << "s.c is now " << +s.c << '\n' // 11 or 00, depending on platform << "s.n is now " << s.n << '\n'; // 12340011 or 00115678 }
Mögliche Ausgabe
s.n = 12345678 s.c is now 0 s.n is now 115678
Jeder Member wird so zugewiesen, als wäre er der einzige Member der Klasse.
|
Wenn Member einer Union Klassen mit benutzerdefinierten Konstruktoren und Destruktoren sind, werden zum Umschalten des aktiven Members im Allgemeinen explizite Destruktoren und Placement-new benötigt. Führen Sie diesen Code aus #include <iostream> #include <string> #include <vector> union S { std::string str; std::vector<int> vec; ~S() {} // needs to know which member is active, only possible in union-like class }; // the whole union occupies max(sizeof(string), sizeof(vector<int>)) int main() { S s = {"Hello, world"}; // at this point, reading from s.vec is undefined behavior std::cout << "s.str = " << s.str << '\n'; s.str.~basic_string(); new (&s.vec) std::vector<int>; // now, s.vec is the active member of the union s.vec.push_back(10); std::cout << s.vec.size() << '\n'; s.vec.~vector(); } Ausgabe s.str = Hello, world 1 |
(seit C++11) |
Wenn zwei Union-Member Standard-Layout-Typen sind, ist es wohl-definiert, ihre gemeinsame Sequenz auf jedem Compiler zu untersuchen.
[bearbeiten] Member-Lebensdauer
Die Lebensdauer eines Union-Members beginnt, wenn der Member aktiv gemacht wird. Wenn zuvor ein anderer Member aktiv war, endet dessen Lebensdauer.
Wenn der aktive Member einer Union durch einen Zuweisungsausdruck der Form E1 = E2 umgeschaltet wird, der entweder den integrierten Zuweisungsoperator oder einen trivialen Zuweisungsoperator verwendet, wird für jeden Union-Member X, der in den Memberzugriffs- und Array-Subskript-Teilausdrücken von E1 vorkommt und keine Klasse mit nicht-trivialen oder gelöschten Standardkonstruktoren ist, wenn eine Modifikation von X unter Typ-Aliasing-Regeln undefiniertes Verhalten hätte, ein Objekt vom Typ von X im benannten Speicher implizit erzeugt; keine Initialisierung wird durchgeführt und der Beginn seiner Lebensdauer wird nach der Wertberechnung der linken und rechten Operanden und vor der Zuweisung sequenziert.
union A { int x; int y[4]; }; struct B { A a; }; union C { B b; int k; }; int f() { C c; // does not start lifetime of any union member c.b.a.y[3] = 4; // OK: "c.b.a.y[3]", names union members c.b and c.b.a.y; // This creates objects to hold union members c.b and c.b.a.y return c.b.a.y[3]; // OK: c.b.a.y refers to newly created object } struct X { const int a; int b; }; union Y { X x; int k; }; void g() { Y y = {{1, 2}}; // OK, y.x is active union member int n = y.x.a; y.k = 4; // OK: ends lifetime of y.x, y.k is active member of union y.x.b = n; // undefined behavior: y.x.b modified outside its lifetime, // "y.x.b" names y.x, but X's default constructor is deleted, // so union member y.x's lifetime does not implicitly start }
Triviale Move-Konstruktor, Move-Zuweisungsoperator, (seit C++11)Kopierkonstruktor und Kopier-Zuweisungsoperator von Union-Typen kopieren Objekt-Repräsentationen. Wenn die Quelle und das Ziel nicht dasselbe Objekt sind, starten diese speziellen Memberfunktionen die Lebensdauer jedes Objekts (außer für Objekte, die weder Unterobjekte des Ziels noch vom Implizit-Lebensdauer-Typ sind), das im Ziel verschachtelt ist und dem im Quellobjekt verschachtelten Objekt entspricht, bevor die Kopie durchgeführt wird. Andernfalls tun sie nichts. Zwei Union-Objekte haben nach der Konstruktion oder Zuweisung über triviale spezielle Funktionen denselben entsprechenden aktiven Member (falls vorhanden).
[bearbeiten] Anonyme Unions
Eine *anonyme Union* ist eine unbenannte Union-Definition, die gleichzeitig keine Variablen (einschließlich Objekte vom Union-Typ, Referenzen oder Zeiger auf die Union) definiert.
union { member-specification } ; |
|||||||||
Anonyme Unions haben weitere Einschränkungen: Sie dürfen keine Memberfunktionen und keine statischen Datenmember haben, und alle ihre Datenmember müssen public sein. Die einzigen erlaubten Deklarationen sind nicht-statische Datenmember und static_assert-Deklarationen(seit C++11).
Member einer anonymen Union werden in den umschließenden Geltungsbereich injiziert (und dürfen nicht mit anderen dort deklarierten Namen kollidieren).
int main() { union { int a; const char* p; }; a = 1; p = "Jennifer"; }
Anonyme Unions im Namensraum-Geltungsbereich müssen als static deklariert werden, es sei denn, sie erscheinen in einem unbenannten Namensraum.
[bearbeiten] Union-ähnliche Klassen
Eine *union-ähnliche Klasse* ist entweder eine Union oder eine (nicht-Union) Klasse, die mindestens eine anonyme Union als Member hat. Eine union-ähnliche Klasse hat eine Menge von *Variantenmembern*:
- die nicht-statischen Datenmember ihrer anonymen Union-Member;
- zusätzlich, wenn die union-ähnliche Klasse eine Union ist, ihre nicht-statischen Datenmember, die keine anonymen Unions sind.
Union-ähnliche Klassen können zur Implementierung von Tagged Unions verwendet werden.
#include <iostream> // S has one non-static data member (tag), three enumerator members (CHAR, INT, DOUBLE), // and three variant members (c, i, d) struct S { enum{CHAR, INT, DOUBLE} tag; union { char c; int i; double d; }; }; void print_s(const S& s) { switch(s.tag) { case S::CHAR: std::cout << s.c << '\n'; break; case S::INT: std::cout << s.i << '\n'; break; case S::DOUBLE: std::cout << s.d << '\n'; break; } } int main() { S s = {S::CHAR, 'a'}; print_s(s); s.tag = S::INT; s.i = 123; print_s(s); }
Ausgabe
a 123
|
Die C++-Standardbibliothek enthält std::variant, das viele Verwendungen von Unions und union-ähnlichen Klassen ersetzen kann. Das obige Beispiel kann umgeschrieben werden als Führen Sie diesen Code aus #include <iostream> #include <variant> int main() { std::variant<char, int, double> s = 'a'; std::visit([](auto x){ std::cout << x << '\n';}, s); s = 123; std::visit([](auto x){ std::cout << x << '\n';}, s); } Ausgabe a 123 |
(seit C++17) |
[bearbeiten] Schlüsselwörter
[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 1940 | C++11 | anonyme Unions erlauben nur nicht-statische Datenmember | static_assert ebenfalls erlaubt |
[bearbeiten] Referenzen
- C++23 Standard (ISO/IEC 14882:2024)
- 11.5 Unions [class.union]
- C++20 Standard (ISO/IEC 14882:2020)
- 11.5 Unions [class.union]
- C++17 Standard (ISO/IEC 14882:2017)
- 12.3 Unions [class.union]
- C++14 Standard (ISO/IEC 14882:2014)
- 9.5 Unions [class.union]
- C++11 Standard (ISO/IEC 14882:2011)
- 9.5 Unions [class.union]
- C++03-Standard (ISO/IEC 14882:2003)
- 9.5 Unions [class.union]
- C++98 Standard (ISO/IEC 14882:1998)
- 9.5 Unions [class.union]
[bearbeiten] Siehe auch
| (C++17) |
eine typsichere diskriminierte Union (Klassentemplate) |
| C-Dokumentation für Union-Deklaration
| |