名前空間
変種
操作

共用体宣言

提供: cppreference.com
< cpp‎ | language
 
 
C++言語
一般的なトピック
フロー制御
条件付き実行文
繰り返し文 (ループ)
ジャンプ文
関数
関数宣言
ラムダ関数宣言
inline 指定子
例外指定 (C++20未満)
noexcept 指定子 (C++11)
例外
名前空間
複合型
共用体型
指定子
decltype (C++11)
auto (C++11)
alignas (C++11)
記憶域期間指定子
初期化
代替表現
リテラル
ブーリアン - 整数 - 浮動小数点
文字 - 文字列 - nullptr (C++11)
ユーザ定義 (C++11)
ユーティリティ
属性 (C++11)
typedef 宣言
型エイリアス宣言 (C++11)
キャスト
暗黙の変換 - 明示的な変換
static_cast - dynamic_cast
const_cast - reinterpret_cast
メモリ確保
クラス
クラス固有の関数特性
特別なメンバ関数
テンプレート
その他
 
 

共用体はその非静的データメンバのうち一度にひとつのみを保持できる特殊なクラス型です。

共用体宣言のためのクラス指定子はクラスまたは構造体の宣言と類似しています。

union attr class-head-name { member-specification }
attr(C++11) - オプショナルな任意個の属性の並び。
class-head-name - 定義される共用体の名前。 前に nested-name-specifier (スコープ解決演算子で終わる名前とスコープ解決演算子の並び) を付けても構いません。 名前は省略でき、その場合その共用体は無名です。
member-specification - アクセス指定子、メンバオブジェクトおよびメンバ関数の宣言および定義の並び。

共用体はメンバ関数 (コンストラクタおよびデストラクタを含みます) を持つことはできますが、仮想関数を持つことはできません。

共用体は基底クラスを持つことはできず、基底クラスとして使用することもできません。

共用体は参照型の非静的データメンバを持つことはできません。

共用体は非トリビアルな特別なメンバ関数 (コピーコンストラクタコピー代入演算子、またはデストラクタ) を持つ非静的データメンバを含むことはできません。

(C++11未満)

共用体が非トリビアルな特別なメンバ関数 (コピー/ムーブコンストラクタ、コピー/ムーブ代入、またはデストラクタ) を持つ非静的データメンバを含む場合、その共用体のその関数はデフォルトで削除され、プログラマによって明示的に定義される必要があります。

共用体が非トリビアルなデフォルトコンストラクタを持つ非静的データメンバを含む場合、その共用体の変種メンバがデフォルトメンバ初期化子を持たない限り、その共用体のデフォルトコンストラクタは削除されます 。

多くとも1個の変種メンバデフォルトメンバ初期化子を持てます。

(C++11以上)

構造体宣言の場合と同じく、共用体のデフォルトメンバアクセスは public です。

目次

[編集] 説明

共用体はその最も大きなデータメンバを保持するために必要なだけの大きさを持ちます。 他のデータメンバはその最も大きなメンバの一部と同じバイトに割り当てられます。 この割り当ての詳細は処理系定義であり、最も最近書き込まれたのでない共用体のメンバからの読み込みは未定義動作です。 多くのコンパイラは、非標準の言語拡張として、共用体の非アクティブメンバから読み込む能力を実装しています。

#include <iostream>
#include <cstdint>
union S
{
    std::int32_t n;     // 4バイトを占めます。
    std::uint16_t s[2]; // 4バイトを占めます。
    std::uint8_t c;     // 1バイトを占めます。
};                      // 共用体全体は4バイトを占めます。
 
int main()
{
    S s = {0x12345678}; // 最初のメンバを初期化します。 s.n がアクティブメンバになります。
    // この時点では s.s や s.c からの読み込みは未定義動作です。
    std::cout << std::hex << "s.n = " << s.n << '\n';
    s.s[0] = 0x0011; // s.s がアクティブメンバになります。
    // この時点では n や c からの読み込みは未定義動作ですが、ほとんどのコンパイラは動作を定義しています。
    std::cout << "s.c is now " << +s.c << '\n' // 11 または 00 (プラットフォーム依存)。
              << "s.n is now " << s.n << '\n'; // 12340011 または 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() {} // どのメンバがアクティブかを知っている必要があります (union ライクなクラスでのみ可能)。
};          // 共用体全体は max(sizeof(string), sizeof(vector<int>)) バイトを占めます。
 
int main()
{
    S s = {"Hello, world"};
    // この時点では s.vec からの読み込みは未定義動作です。
    std::cout << "s.str = " << s.str << '\n';
    s.str.~basic_string();
    new (&s.vec) std::vector<int>;
    // s.vec が共用体のアクティブメンバになりました。
    s.vec.push_back(10);
    std::cout << s.vec.size() << '\n';
    s.vec.~vector();
}

出力:

s.str = Hello, world
1
(C++11以上)

共用体の2つのメンバが標準レイアウト型の場合、その共通部分列を調べることはいかなるコンパイラにおいても well-defined です。

[編集] メンバの生存期間

共用体のメンバの生存期間は、そのメンバがアクティブになったときに開始します。 別のメンバが以前アクティブであった場合は、その生存期間は終了します。

組み込みの代入演算子またはトリビアルな代入演算子のいずれかを用いた E1 = E2 形式の代入式によって共用体のアクティブメンバが切り替わったとき、非トリビアルなまたは削除されたデフォルトコンストラクタを持つクラスでない E1 のメンバアクセスおよび配列添字部分式に現れる共用体メンバ X のそれぞれについて、もし X の変更が型エイリアシングルールの元で未定義動作となるであろう場合、 X の型のオブジェクトがその記憶域に暗黙に作成されます。 初期化は行われず、その生存期間の開始は左右の被演算子の値計算の後かつ代入の前に配列されます。

union A { int x; int y[4]; };
struct B { A a; };
union C { B b; int k; };
int f() {
  C c;               // いかなる共用体メンバの生存期間も開始しません。
  c.b.a.y[3] = 4;    // OK、「c.b.a.y[3]」は共用体メンバ c.b および c.b.a.y を参照します。
                     // これは共用体メンバ c.b および c.b.a.y を保持するためのオブジェクトを作成します。
  return c.b.a.y[3]; // OK、 c.b.a.y は新たに作成されたオブジェクトを参照します。
}
 
struct X { const int a; int b; };
union Y { X x; int k; };
void g() {
  Y y = { { 1, 2 } }; // OK、 y.x がアクティブな共用体メンバです (9.2)。
  int n = y.x.a;
  y.k = 4;   // OK、 y.x の生存期間が終了し、 y.k が共用体のアクティブメンバになります。
  y.x.b = n; // 未定義動作。 y.x.b がその生存期間外で変更されました。
             // 「y.x.b」は y.x を参照しますが、 X のデフォルトコンストラクタは
             // 削除されているため、共用体メンバ y.d の生存期間は暗黙に開始しません。
}

[編集] 無名共用体

無名共用体は変数 (その共用体型のオブジェクト、その共用体への参照またはポインタを含みます) を定義しない無名共用体の定義です。

union { member-specification } ;

無名共用体には追加の制限があります。 メンバ関数を持つことはできず、静的データメンバは持つことができず、そのデータメンバはすべてパブリックでなければなりません。 許される宣言は非静的データメンバおよび static_assert 宣言 (C++14以上)だけです。

無名共用体のメンバは囲っているスコープに注入されます (そこで宣言される他の名前と衝突してはなりません)。

int main()
{
    union
    {
        int a;
        const char* p;
    };
    a = 1;
    p = "Jennifer";
}

名前空間スコープの無名共用体は、無名名前空間内に現れる場合を除いて、 static 宣言されなければなりません。

[編集] union ライクなクラス

union ライクなクラスは、メンバとして少なくともひとつの無名共用体を持つ (共用体でない) クラスか、共用体かの、いずれかです。 union ライクなクラスは変種メンバの集合を持ち��す。 以下のものが変種メンバです。

  • そのメンバ無名共用体の非静的データメンバ。
  • union ライクなクラスが共用体の場合は、その無名共用体でない非静的データメンバ。

union ライクなクラスはタグ付き共用体を実装するために使用できます。

#include <iostream>
 
// S は1個の非静的データメンバ (tag)、3個の列挙子メンバ (CHAR、INT、DOUBLE)、
// および3個の変種メンバ (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 が含まれており、共用体および union ライクなクラスの多くの用途を置き換えることができます。 上の例は以下のように書き直すことができます。

#include <variant>
#include <iostream>
 
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++14 anonymous unions only allowed non-static data members static_assert also allowed

[編集] 関連項目

共用体宣言C言語リファレンス