共用体宣言
共用体(union)は、その非静的 データメンバ のうち一つだけを一度に保持できる特殊なクラス型です。
目次 |
[編集] 構文
共用体宣言のクラス指定子は、class や struct の宣言に似ています。
union attr class-head-name { member-specification } |
|||||||||
| attr | - | (C++11以降) 0個以上の属性からなる、省略可能なシーケンス |
| class-head-name | - | 定義される共用体の名前。省略可能で nested-name-specifier (名前とスコープ解決演算子のシーケンスで、スコープ解決演算子で終わるもの) を前に付けることができます。名前は省略可能で、その場合、共用体は *無名* となります。 |
| member-specification | - | アクセス指定子、メンバオブジェクトおよびメンバ関数の宣言と定義のリスト。 |
共用体はメンバ関数(コンストラクタとデストラクタを含む)を持つことができますが、仮想関数は持てません。
共用体は基底クラスを持つことができず、基底クラスとして使用することもできません。
|
最大で1つのバリアントメンバがデフォルトメンバ初期化子を持つことができます。 |
(C++11以降) |
共用体は参照型の非静的データメンバを持つことができません。
|
共用体は、非トリビアルな特殊メンバ関数を持つ非静的データメンバを含むことはできません。 |
(C++11まで) |
|
共用体が非トリビアルな特殊メンバ関数を持つ非静的データメンバを含む場合、共用体の対応する特殊メンバ関数は deleted として定義されることがあります。詳細は各特殊メンバ関数のページを参照してください。 |
(C++11以降) |
struct 宣言と同様に、共用体におけるデフォルトのメンバアクセスは public です。
[編集] 説明
共用体のサイズは、その最大のデータメンバを保持するために必要な大きさ以上になりますが、通常はそれより大きくはなりません。他のデータメンバは、その最大のメンバの一部として同じバイト領域に割り当てられることを意図しています。その割り当ての詳細は実装定義ですが、全ての非静的データメンバが同じアドレスを持つことだけは保証されます。最後に書き込まれたメンバ以外の共用体のメンバから読み出すことは、未定義動作です。多くのコンパイラは、非標準の言語拡張として、非アクティブな共用体メンバを読み取る機能を実装しています。
#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 }
実行結果の例
s.n = 12345678 s.c is now 0 s.n is now 115678
各メンバは、それがクラスの唯一のメンバであるかのように割り当てられます。
|
共用体のメンバがユーザ定義のコンストラクタやデストラクタを持つクラスである場合、アクティブなメンバを切り替えるには、通常、明示的なデストラクタ呼び出しと配置newが必要です。 このコードを実行 #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(); } 出力 s.str = Hello, world 1 |
(C++11以降) |
2つの共用体メンバが標準レイアウト型である場合、どのコンパイラでもそれらの共通部分を調べることは明確に定義されています。
[編集] メンバの生存期間
共用体メンバの生存期間は、そのメンバがアクティブになったときに始まります。もし以前に別のメンバがアクティブだった場合、その生存期間は終了します。
組込み代入演算子またはトリビアルな代入演算子を使用する E1 = E2 形式の代入式によって共用体のアクティブメンバが切り替えられる際、E1 のメンバアクセスおよび配列添字の部分式に現れる各共用体メンバ X (非トリビアルまたは deleted なデフォルトコンストラクタを持つクラスではない) について、もし型エイリアシング規則の下で X の変更が未定義動作となる場合、指定されたストレージに X の型のオブジェクトが暗黙的に作成されます。初期化は行われず、その生存期間の開始は、左辺および右辺オペランドの値計算の後、かつ代入の前に順序付けられます。
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 }
共用体型のトリビアルなムーブコンストラクタ、ムーブ代入演算子、(C++11以降)コピーコンストラクタ、およびコピー代入演算子は、オブジェクト表現をコピーします。コピー元とコピー先が同じオブジェクトでない場合、これらの特殊メンバ関数は、コピーが実行される前に、コピー元に入れ子になったオブジェクトに対応する、コピー先に入れ子になった全てのオブジェクト(コピー先のサブオブジェクトでも暗黙の生存期間型でもないオブジェクトを除く)の生存期間を開始します。そうでなければ、何もしません。2つの共用体オブジェクトは、トリビアルな特殊関数による構築または代入の後、(もしあれば)同じ対応するアクティブメンバを持ちます。
[編集] 無名共用体
*無名共用体* とは、名前のない共用体定義であり、同時にいかなる変数(共用体型のオブジェクト、参照、または共用体へのポインタを含む)も定義しないものです。
union { member-specification } ; |
|||||||||
無名共用体にはさらなる制約があります:メンバ関数を持つことはできず、静的データメンバも持てず、全てのデータメンバは public でなければなりません。許可される宣言は非静的データメンバと static_assert 宣言(C++11以降)のみです。
無名共用体のメンバは、それを取り囲むスコープに注入されます(そして、そこで宣言された他の名前と競合してはなりません)。
int main() { union { int a; const char* p; }; a = 1; p = "Jennifer"; }
名前空間スコープの無名共用体は、無名名前空間内に現れない限り、static として宣言されなければなりません。
[編集] 共用体ライクなクラス
*共用体ライクなクラス* とは、共用体、または少なくとも1つの無名共用体をメンバとして持つ(共用体でない)クラスのことです。共用体ライクなクラスは、*バリアントメンバ* の集合を持ちます。
- そのメンバである無名共用体の非静的データメンバ。
- 加えて、共用体ライクなクラスが共用体である場合、その非静的データメンバのうち無名共用体でないもの。
共用体ライクなクラスは、タグ付き共用体 を実装するために使用できます。
#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); }
出力
a 123
|
C++標準ライブラリには std::variant が含まれており、これは共用体や共用体ライクなクラスの多くの用途を置き換えることができます。上記の例は次のように書き直すことができます。 このコードを実行 #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); } 出力 a 123 |
(C++17以降) |
[編集] キーワード
[編集] 欠陥報告
以下の動作変更を伴う欠陥報告が、以前に公開されたC++標準に遡って適用されました。
| DR | 適用対象 | 公開された動作 | 正しい動作 |
|---|---|---|---|
| CWG 1940 | C++11 | 無名共用体は非静的データメンバのみを許可していた | static_assert も許可された |
[編集] 参照
- C++23標準 (ISO/IEC 14882:2024)
- 11.5 共用体 [class.union]
- C++20 standard (ISO/IEC 14882:2020)
- 11.5 共用体 [class.union]
- C++17 standard (ISO/IEC 14882:2017)
- 12.3 共用体 [class.union]
- C++14 standard (ISO/IEC 14882:2014)
- 9.5 共用体 [class.union]
- C++11 standard (ISO/IEC 14882:2011)
- 9.5 共用体 [class.union]
- C++03 標準 (ISO/IEC 14882:2003)
- 9.5 共用体 [class.union]
- C++98 標準 (ISO/IEC 14882:1998)
- 9.5 共用体 [class.union]
[編集] 関連項目
| (C++17) |
型安全な判別共用体 (クラステンプレート) |
| 共用体宣言のC言語ドキュメント
| |