注
C++17 以降では、 std::variantclass は unionのタイプ セーフな代替手段です。
unionは、すべてのメンバーが同じメモリ位置を共有するユーザー定義型です。 この定義は、 union がメンバーのリストから複数のオブジェクトを含めることができることを意味します。 また、 union が持つメンバーの数に関係なく、常に最大のメンバーを格納するのに十分なメモリのみが使用されることを意味します。
unionは、多数のオブジェクトがあり、メモリが限られている場合にメモリを節約するのに役立ちます。 ただし、 union では、正しく使用するために特別な注意が必要です。 割り当てたのと同じメンバーに常にアクセスすることを保証する責任があります。 いずれかのメンバー型に非トリガー コンストラクターがある場合は、そのメンバーを明示的に構築して破棄するコードを記述する必要があります。 unionを使用する前に、解決しようとしている問題を、基本classと派生class型を使用して、より適切に表現できるかどうかを検討してください。
構文
uniontag選ぶ{member-list};
パラメーター
tag
unionに指定された型名。
member-list
unionに含めることができるメンバー。
を宣言します。 union
union キーワードを使用してunionの宣言を開始し、メンバー リストを中かっこで囲みます。
// 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
}
を使用します。 union
前の例では、 union にアクセスするコードは、データを保持するメンバーを認識する必要があります。 この問題に対する最も一般的な解決策は、 判別 unionと呼ばれます。 unionをstructで囲み、unionに現在格納されているメンバー型を示すenum メンバーが含まれます。 次の例は、基本的なパターンを示しています。
#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;
}
前の例では、Inputstructのunionには名前がないため、匿名unionと呼ばれます。 そのメンバーは、 structのメンバーであるかのように直接アクセスできます。 匿名unionの使用方法の詳細については、「匿名union」セクションを参照してください。
前の例は、共通の基本classから派生したclass型を使用して解決できる問題を示しています。 コンテナー内の各オブジェクトのランタイム型に基づいてコードを分岐できます。 コードの保守と理解は容易ですが、 unionを使用するよりも時間がかかる場合もあります。 また、 unionを使用すると、関連のない型を格納できます。
unionを使用すると、union変数自体の型を変更することなく、格納されている値の型を動的に変更できます。 たとえば、要素が異なる型の異なる値を格納する MyUnionTypeの異種配列を作成できます。
この例では、 Inputstruct を誤用するのは簡単です。 データを保持するメンバーに正しくアクセスするには、ユーザーが識別子を使用する必要があります。 次の例に示すように、 unionprivate を作成し、特別なアクセス機能を提供することで、誤用から保護できます。
無制限の union (C++11)
C++03 以前では、 union には、ユーザーが指定したコンストラクター、デストラクター、または代入演算子がない限り、 class 型を持つ非静的データ メンバーを含めることができます。 C++11 では、これらの制限は削除されます。 このようなメンバーを unionに含める場合、コンパイラは、ユーザーが指定していない特殊なメンバー関数を deletedとして自動的にマークします。
unionがclassまたはstruct内の匿名unionである場合、ユーザーが指定していないclassまたはstructの特別なメンバー関数は、deletedとしてマークされます。 次の例は、このケースを処理する方法を示しています。
unionのメンバーの 1 人に、この特別な扱いを必要とするメンバーがいます。
// 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;
}
unionは参照を格納できません。 unionでは継承もサポートされていません。 つまり、基本classとしてunionを使用したり、別のclassから継承したり、仮想関数を持ったりすることはできません。
を初期化します。 union
中かっこで囲まれた式を割り当てることで、同じステートメントで union を宣言および初期化できます。 式が評価され、 unionの最初のフィールドに割り当てられます。
#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
*/
NumericType
unionは、次の図に示すようにメモリ内に配置されます (概念的には)。
この図は、8 バイトのデータを示しています。 double 型 dValue は、8 バイト全体を占有します。 型 long lValue は最初の 4 バイトを占有します。 short 型 iValue は最初のバイトを占有します。
アノニマス union
匿名 union は、 class-name または declarator-listなしで宣言されたです。
union {member-list}
匿名 union で宣言された名前は、非メンバー変数と同様に直接使用されます。 これは、匿名 union で宣言された名前が、周囲のスコープで一意である必要があることを意味します。
匿名 union には、次の制限が適用されます。
- ファイルまたは名前空間のスコープで宣言する場合は、
staticとして宣言する必要もあります。 -
publicメンバーのみを含めることができます。匿名unionにメンバーをprivateしてprotectedすると、エラーが発生します。 - メンバー関数を含めることはできません。