Namespaces
Variants

Union declaration

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

unionは、一度に非静的 data members のうち1つしか保持できない特殊なクラス型です。

目次

構文

union宣言のクラス指定子は、 classまたはstruct の宣言と同様です:

union attr class-head-name { member-specification }
(注:指定された条件により、HTMLタグ、属性、 タグ内のテキスト、C++固有の用語は翻訳せず、元のフォーマットを保持しています。このコードスニペットはC++のunion宣言の構文を示しており、翻訳対象となる自然言語テキストが含まれていないため、出力は入力と同一になります。)
attr - (since C++11) 任意の数の attributes のオプションのシーケンス
class-head-name - 定義されているunionの名前。オプションで nested-name-specifier (名前とスコープ解決演算子のシーケンスで、スコープ解決演算子で終わる)が前置される。名前は省略可能であり、その場合unionは 無名 となる
member-specification - アクセス指定子、メンバオブジェクトおよびメンバ関数の宣言と定義のリスト

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

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

最大で一つの variant member のみが default member initializer を持つことができます。

(C++11以降)

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

共用体は非トリビアルな 特殊メンバ関数 を持つ非静的データメンバを含むことはできません。

(C++11まで)

共用体が非トリビアルな 特殊メンバ関数 を持つ非静的データメンバを含む場合、対応する共用体の特殊メンバ関数は削除済みとして定義される可能性があります。詳細については対応する特殊メンバ関数のページを参照してください。

(C++11以降)

struct の宣言と同様に、unionにおけるデフォルトのメンバアクセスは public です。

説明

共用体は、その最大のデータメンバを保持するのに必要なサイズ以上を持ちますが、通常それ以上にはなりません。他のデータメンバは、その最大メンバの一部として同じバイト内に割り当てられることを意図しています。その割り当ての詳細は実装定義ですが、すべての非静的データメンバが同じアドレスを持つ点は除きます。最も最近書き込まれたメンバ以外の共用体メンバから読み取ることは未定義動作です。多くのコンパイラは、非標準の言語拡張として、共用体の非アクティブなメンバを読み取る機能を実装しています。

#include <cstdint>
#include <iostream>
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が現在アクティブなメンバ
    // この時点でs.nまたはs.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() {} // 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つの共用体メンバーが standard-layout 型である場合、それらの共通部分列をどのコンパイラでも検査することは well-defined です。

メンバーの生存期間

共用体のメンバの 生存期間 は、そのメンバがアクティブになった時点から始まります。以前に他のメンバがアクティブであった場合、そのメンバの生存期間は終了します。

共用体のアクティブなメンバーが、組み込みの代入演算子または自明な代入演算子を使用する E1 = E2 の形式の代入式によって切り替えられる場合、 E1 のメンバーアクセスおよび配列添字部分式に現れる各共用体メンバーXについて、Xが非自明または削除されたデフォルトコンストラクタを持つクラスでない場合、Xの変更が型エイリアシング規則の下で未定義動作となるならば、Xの型のオブジェクトが指定されたストレージに暗黙的に作成される。初期化は行われず、その生存期間の開始は左辺と右辺のオペランドの値計算の後、代入の前に配列される。

union A { int x; int y[4]; };
struct B { A a; };
union C { B b; int k; };
int f()
{
    C c;               // いずれのunionメンバーの生存期間も開始しない
    c.b.a.y[3] = 4;    // OK: "c.b.a.y[3]"はunionメンバーc.bとc.b.a.yを指定
                       // これによりunionメンバー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はアクティブなunionメンバー
    int n = y.x.a;
    y.k = 4;   // OK: y.xの生存期間を終了、y.kがunionのアクティブメンバーとなる
    y.x.b = n; // 未定義動作: y.x.bがその生存期間外で変更された、
               // "y.x.b"はy.xを指定するが、Xのデフォルトコンストラクタは削除されているため、
               // unionメンバーy.xの生存期間は暗黙的に開始されない
}

自明な ムーブコンストラクタ、ムーブ代入演算子、 (C++11以降) コピーコンストラクタおよびコピー代入演算子は、共用体型のオブジェクト表現をコピーします。ソースとデスティネーションが同一オブジェクトでない場合、これらの特殊メンバ関数は、コピー実行前にソース内にネストされた各オブジェクトに対応するデスティネーション内にネストされたオブジェクト(デスティネーションのサブオブジェクトでもなく 暗黙的ライフタイム型 でもないオブジェクトを除く)のライフタイムを開始します。それ以外の場合、これらは何も行いません。2つの共用体オブジェクトは、自明な特殊関数による構築または代入後、同じ対応するアクティブメンバ(存在する場合)を持ちます。

匿名共用体

匿名共用体 とは、変数(共用体型のオブジェクト、参照、共用体へのポインタを含む)を同時に定義しない無名の共用体定義です。

union { メンバー仕様 } ;

匿名共用体にはさらなる制限があります: メンバ関数を持つことができず、静的データメンバを持つことができず、すべてのデータメンバはpublicでなければなりません。許可される宣言は非静的データメンバ および static_assert 宣言 (C++11以降) のみです。

無名共用体のメンバーは外側のスコープに注入され(そこで宣言された他の名前と競合してはならない)。

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

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

Union風クラス

union-like class は、unionであるか、または(非unionの)クラスで少なくとも1つの匿名unionをメンバーとして持つものです。union-like classは一連の variant members  を持ちます:

  • そのメンバ匿名共用体の非静的データメンバー
  • さらに、共用体ライククラスが共用体である場合、匿名共用体ではないその非静的データメンバー

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

#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 標準 (ISO/IEC 14882:2020):
  • 11.5 共用体 [class.union]
  • C++17規格 (ISO/IEC 14882:2017):
  • 12.3 共用体 [class.union]
  • C++14標準 (ISO/IEC 14882:2014):
  • 9.5 共用体 [class.union]
  • C++11標準 (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ドキュメント for Union宣言