名前空間
変種
操作

共用体宣言

From cppreference.com
< cpp‎ | language
 
 
C++言語
全般
フロー制御
条件実行文
if
繰り返し文 (ループ)
for
範囲for (C++11)
ジャンプ文
関数
関数宣言
ラムダ式
inline指定子
動的例外仕様 (C++17まで*)
noexcept指定子 (C++11)
例外
名前空間
クラス/構造体型
共用体型

指定子
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
記憶域期間指定子
初期化
代替表現
リテラル
ブーリアン - 整数 - 浮動小数点数
文字 - 文字列 - nullptr (C++11)
ユーザー定義 (C++11)
ユーティリティ
属性 (C++11)
typedef宣言
型エイリアス宣言 (C++11)
キャスト
メモリ確保
クラス
クラス固有の関数プロパティ
explicit (C++11)
static

特殊メンバ関数
テンプレート
その他
 
 

共用体(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以降)

[編集] キーワード

union

[編集] 欠陥報告

以下の動作変更を伴う欠陥報告が、以前に公開された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言語ドキュメント
English 日本語 中文(简体) 中文(繁體)