Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Hinweis
Ab C++17 ist std::variant
class eine typsichere Alternative zu einem union-Element.
Bei einem union
-Element handelt es sich um einen benutzerdefinierten Typ, in dem alle Member den gleichen Speicherbereich verwenden. Diese Definition bedeutet, dass ein union-Element niemals mehr als ein Objekt aus seiner Liste der Member enthalten kann. Es bedeutet außerdem, dass immer nur so viel Arbeitsspeicher beansprucht wird, wie nötig ist, um den größten Member zu speichern – unabhängig davon, über wie viele Member ein union-Element verfügt.
Ein union-Element kann dazu beitragen, Arbeitsspeicher zu sparen, wenn Sie über zahlreiche Objekte verfügen und der Arbeitsspeicher begrenzt ist. Die korrekte Verwendung eines union-Elements erfordert jedoch besondere Sorgfalt. Sie müssen sicherstellen, dass Sie immer auf den gleichen Member zugreifen, den Sie zugewiesen haben. Wenn Membertypen über einen nicht trivialen Konstruktor verfügen, müssen Sie Code schreiben, um dieses Member explizit zu erstellen und zu zerstören. Prüfen Sie vor der Verwendung eines union-Elements, ob das zu lösende Problem nicht besser durch die Verwendung einer Basisklasse und abgeleiteter Klassentypen ausgedrückt werden kann.
Syntax
union
tag
opt{
member-list
};
Parameter
tag
Der Typname, der für das union-Element angegeben wurde.
member-list
Member, die das union-Element enthalten kann.
Deklarieren eines union-Elements
Beginnen Sie die Deklaration eines union-Elements mit dem Schlüsselwort union
, und schließen Sie die Memberliste in geschweifte Klammern ein:
// declaring_a_union.cpp
union RecordType // Declare a simple union type
{
char ch;
int i;
long l;
float f;
double d;
int *int_ptr;
};
int main()
{
RecordType t;
t.i = 5; // t holds an int
t.f = 7.25; // t now holds a float
}
Verwenden eines union-Elements
Im vorherigen Beispiel muss der Code, der auf das union-Element zugreift, wissen, welcher Member die Daten enthält. Die gängigste Lösung für dieses Problem wird als diskriminiertes union-Element bezeichnet. Es schließt das union-Element in eine Struktur (struct) ein und enthält einen enum-Member, der den Membertyp angibt, der derzeit im union-Element gespeichert ist. Das grundlegende Muster wird im folgenden Beispiel veranschaulicht:
#include <queue>
using namespace std;
enum class WeatherDataType
{
Temperature, Wind
};
struct TempData
{
int StationId;
time_t time;
double current;
double max;
double min;
};
struct WindData
{
int StationId;
time_t time;
int speed;
short direction;
};
struct Input
{
WeatherDataType type;
union
{
TempData temp;
WindData wind;
};
};
// Functions that are specific to data types
void Process_Temp(TempData t) {}
void Process_Wind(WindData w) {}
void Initialize(std::queue<Input>& inputs)
{
Input first;
first.type = WeatherDataType::Temperature;
first.temp = { 101, 1418855664, 91.8, 108.5, 67.2 };
inputs.push(first);
Input second;
second.type = WeatherDataType::Wind;
second.wind = { 204, 1418859354, 14, 27 };
inputs.push(second);
}
int main(int argc, char* argv[])
{
// Container for all the data records
queue<Input> inputs;
Initialize(inputs);
while (!inputs.empty())
{
Input const i = inputs.front();
switch (i.type)
{
case WeatherDataType::Temperature:
Process_Temp(i.temp);
break;
case WeatherDataType::Wind:
Process_Wind(i.wind);
break;
default:
break;
}
inputs.pop();
}
return 0;
}
Im vorherigen Beispiel hat das union-Element in der Eingabestruktur (Input
struct) keinen Namen und wird daher wird als anonymes -Elementunion bezeichnet. Auf seine Member kann direkt zugegriffen werden – genau wie auf Member der Struktur (struct). Weitere Informationen zur Verwendung eines anonymen union-Elements finden Sie im Abschnitt Anonymes union-Element.
Das vorherige Beispiel zeigt ein Problem, das Sie auch mithilfe von Klassentypen lösen können, die von einer gemeinsamen Basisklasse abgeleitet werden. Sie können Ihren Code basierend auf dem Runtimetyp des jeweiligen Objekts im Container verzweigen. Ihr Code wäre dann ggf. einfacher zu verwalten und leichter verständlich, möglicherweise aber auch langsamer als bei Verwendung eines union-Elements. Außerdem können mit einem union-Element unzusammenhängende Typen gespeichert werden. Mit einem union-Element können Sie den Typ des gespeicherten Werts dynamisch ändern, ohne den Typ der union-Variablen selbst zu ändern. So können Sie beispielsweise ein heterogenes Array von MyUnionType
erstellen, dessen Elemente unterschiedliche Werte mit unterschiedlichen Typen speichern.
Die Eingabestruktur (Input
struct) kann in dem Beispiel leicht missbraucht werden. Der Benutzer ist dafür verantwortlich, den Diskriminator richtig zu verwenden, um auf den Member zuzugreifen, der die Daten enthält. Sie können sich vor Missbrauch schützen, indem Sie das union-Element auf private
festlegen und besondere Zugriffsfunktionen bereitstellen, wie im nächsten Beispiel gezeigt.
Uneingeschränktes union-Element (C++11)
In C++03 und früher kann eine union nichtstatische Datenelemente enthalten, die einen class Typ haben, solange der Typ keine vom Benutzer bereitgestellten Konstruktoren, Destruktoren oder Zuweisungsoperatoren hat. In C++11 wurden diese Einschränkungen entfernt. Wenn Sie einen derartigen Member in Ihr union-Element einbeziehen, markiert der Compiler automatisch jede besondere Memberfunktion, die nicht vom Benutzer bereitgestellt wurde, als gelöscht (deleted
). Wenn es sich bei dem union-Element um ein anonymes union-Element innerhalb einer Klasse (class) oder einer Struktur (struct) handelt, werden alle speziellen Memberfunktionen der Klasse (class) oder Struktur (struct), die nicht vom Benutzer bereitgestellt wurden, als gelöscht (deleted
) gekennzeichnet. Das folgende Beispiel zeigt die Behandlung eines solchen Falls. Einer der Member des union-Elements verfügt über einen Member, der diese Sonderbehandlung erfordert:
// for MyVariant
#include <crtdbg.h>
#include <new>
#include <utility>
// for sample objects and output
#include <string>
#include <vector>
#include <iostream>
using namespace std;
struct A
{
A() = default;
A(int i, const string& str) : num(i), name(str) {}
int num;
string name;
//...
};
struct B
{
B() = default;
B(int i, const string& str) : num(i), name(str) {}
int num;
string name;
vector<int> vec;
// ...
};
enum class Kind { None, A, B, Integer };
#pragma warning (push)
#pragma warning(disable:4624)
class MyVariant
{
public:
MyVariant()
: kind_(Kind::None)
{
}
MyVariant(Kind kind)
: kind_(kind)
{
switch (kind_)
{
case Kind::None:
break;
case Kind::A:
new (&a_) A();
break;
case Kind::B:
new (&b_) B();
break;
case Kind::Integer:
i_ = 0;
break;
default:
_ASSERT(false);
break;
}
}
~MyVariant()
{
switch (kind_)
{
case Kind::None:
break;
case Kind::A:
a_.~A();
break;
case Kind::B:
b_.~B();
break;
case Kind::Integer:
break;
default:
_ASSERT(false);
break;
}
kind_ = Kind::None;
}
MyVariant(const MyVariant& other)
: kind_(other.kind_)
{
switch (kind_)
{
case Kind::None:
break;
case Kind::A:
new (&a_) A(other.a_);
break;
case Kind::B:
new (&b_) B(other.b_);
break;
case Kind::Integer:
i_ = other.i_;
break;
default:
_ASSERT(false);
break;
}
}
MyVariant(MyVariant&& other)
: kind_(other.kind_)
{
switch (kind_)
{
case Kind::None:
break;
case Kind::A:
new (&a_) A(move(other.a_));
break;
case Kind::B:
new (&b_) B(move(other.b_));
break;
case Kind::Integer:
i_ = other.i_;
break;
default:
_ASSERT(false);
break;
}
other.kind_ = Kind::None;
}
MyVariant& operator=(const MyVariant& other)
{
if (&other != this)
{
switch (other.kind_)
{
case Kind::None:
this->~MyVariant();
break;
case Kind::A:
*this = other.a_;
break;
case Kind::B:
*this = other.b_;
break;
case Kind::Integer:
*this = other.i_;
break;
default:
_ASSERT(false);
break;
}
}
return *this;
}
MyVariant& operator=(MyVariant&& other)
{
_ASSERT(this != &other);
switch (other.kind_)
{
case Kind::None:
this->~MyVariant();
break;
case Kind::A:
*this = move(other.a_);
break;
case Kind::B:
*this = move(other.b_);
break;
case Kind::Integer:
*this = other.i_;
break;
default:
_ASSERT(false);
break;
}
other.kind_ = Kind::None;
return *this;
}
MyVariant(const A& a)
: kind_(Kind::A), a_(a)
{
}
MyVariant(A&& a)
: kind_(Kind::A), a_(move(a))
{
}
MyVariant& operator=(const A& a)
{
if (kind_ != Kind::A)
{
this->~MyVariant();
new (this) MyVariant(a);
}
else
{
a_ = a;
}
return *this;
}
MyVariant& operator=(A&& a)
{
if (kind_ != Kind::A)
{
this->~MyVariant();
new (this) MyVariant(move(a));
}
else
{
a_ = move(a);
}
return *this;
}
MyVariant(const B& b)
: kind_(Kind::B), b_(b)
{
}
MyVariant(B&& b)
: kind_(Kind::B), b_(move(b))
{
}
MyVariant& operator=(const B& b)
{
if (kind_ != Kind::B)
{
this->~MyVariant();
new (this) MyVariant(b);
}
else
{
b_ = b;
}
return *this;
}
MyVariant& operator=(B&& b)
{
if (kind_ != Kind::B)
{
this->~MyVariant();
new (this) MyVariant(move(b));
}
else
{
b_ = move(b);
}
return *this;
}
MyVariant(int i)
: kind_(Kind::Integer), i_(i)
{
}
MyVariant& operator=(int i)
{
if (kind_ != Kind::Integer)
{
this->~MyVariant();
new (this) MyVariant(i);
}
else
{
i_ = i;
}
return *this;
}
Kind GetKind() const
{
return kind_;
}
A& GetA()
{
_ASSERT(kind_ == Kind::A);
return a_;
}
const A& GetA() const
{
_ASSERT(kind_ == Kind::A);
return a_;
}
B& GetB()
{
_ASSERT(kind_ == Kind::B);
return b_;
}
const B& GetB() const
{
_ASSERT(kind_ == Kind::B);
return b_;
}
int& GetInteger()
{
_ASSERT(kind_ == Kind::Integer);
return i_;
}
const int& GetInteger() const
{
_ASSERT(kind_ == Kind::Integer);
return i_;
}
private:
Kind kind_;
union
{
A a_;
B b_;
int i_;
};
};
#pragma warning (pop)
int main()
{
A a(1, "Hello from A");
B b(2, "Hello from B");
MyVariant mv_1 = a;
cout << "mv_1 = a: " << mv_1.GetA().name << endl;
mv_1 = b;
cout << "mv_1 = b: " << mv_1.GetB().name << endl;
mv_1 = A(3, "hello again from A");
cout << R"aaa(mv_1 = A(3, "hello again from A"): )aaa" << mv_1.GetA().name << endl;
mv_1 = 42;
cout << "mv_1 = 42: " << mv_1.GetInteger() << endl;
b.vec = { 10,20,30,40,50 };
mv_1 = move(b);
cout << "After move, mv_1 = b: vec.size = " << mv_1.GetB().vec.size() << endl;
cout << endl << "Press a letter" << endl;
char c;
cin >> c;
}
Ein union-Element kann keinen Verweis speichern. Außerdem unterstützt ein union-Element keine Vererbung. Das bedeutet, dass Sie kein union-Element als Basisklasse verwenden, nicht von einer anderen Klasse erben und keine virtuellen Funktionen verwenden können.
Initialisieren eines union-Elements
Sie können ein union-Element innerhalb der gleichen Anweisung deklarieren und initialisieren, indem Sie einen Ausdruck zuweisen, der in geschweifte Klammern eingeschlossen ist. Der Ausdruck wird ausgewertet und dem ersten Feld des union-Elements zugewiesen.
#include <iostream>
using namespace std;
union NumericType
{
short iValue;
long lValue;
double dValue;
};
int main()
{
union NumericType Values = { 10 }; // iValue = 10
cout << Values.iValue << endl;
Values.dValue = 3.1416;
cout << Values.dValue << endl;
}
/* Output:
10
3.141600
*/
Die union-Element NumericType
wird im Arbeitsspeicher (konzeptionell) wie in der folgenden Abbildung angeordnet:
Das Diagramm zeigt Daten mit einer Größe von acht Bytes. Der Double-Typ „dValue“ belegt die gesamten acht Bytes. Der lange Typ „lValue“ belegt die ersten vier Bytes. Der kurze Typ „iValue“ belegt das erste Byte.
Anonymes union-Element
Ein anonymes union-Element wird ohne class-name
oder declarator-list
deklariert.
union {
member-list
}
Die in einem anonymen union-Element deklarierten Namen werden, wie Nichtmembervariablen, direkt verwendet. Daher müssen die in einem anonymen union-Element deklarierten Namen im umgebenden Bereich eindeutig sein.
Für anonyme union-Elemente gelten folgende Einschränkungen:
- Wenn sie im Datei- oder Namespacebereich deklariert werden, müssen sie auch als
static
deklariert werden. - Sie können nur Member vom Typ
public
haben. Member vom Typprivate
oderprotected
in einem anonymen union-Element führen zu Fehlern. - Sie dürfen über keine Memberfunktionen verfügen.