union

Note

C++17 以降では、std::variantclass が union のタイプ セーフな代替手段です。

union とは、すべてのメンバーが同じメモリの場所を共有するユーザー定義の型です。 この定義は、任意のどの時点においても、union はそのメンバー一覧にあるオブジェクトを 1 つだけ含むことができることを意味します。 また、union のメンバー数には関係なく、最大のメンバーを格納するのに必要なメモリだけが常に使用されることも意味します。

union は、多数のオブジェクトやメモリの制限がある場合、メモリの節約に役立ちます。 ただし、union を正しく使用するには特別な注意が必要です。 割り当てたのと同じメンバーに常にアクセスする必要があります。 いずれかのメンバー型に非トリガー constructがある場合、またはそのメンバーを明示的に結合struct して破棄するコードを記述する必要があります。 union を使用する前に、解決すべき問題が、基本データ型 class および派生 class 型を使用することでより適切に表現できないか検討します。

構文

uniontagopt{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 を 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 の誤用が簡単に発生します。 データを保持するメンバーにアクセスするには、ユーザーが識別子を正しく使用する必要があります。 次の例で示すように、union を private にし、特別なアクセス関数を指定することで、誤用を防ぐことができます。

制限されていない union (C++11)

C++03 以前では、union型にユーザーが指定した class constructors、destructors、または代入演算子がない限り、a には型を持つ非staticデータ メンバーを含めることができます。 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 は、参照を格納できません。 また、A union は継承をサポートしていません。 つまり、union を基底 class として使用したり、別の 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
*/

次の NumericTypeunion 図に示すように、(概念的には) メモリ内に配置されます。

Diagram that shows the overlapping storage of data in the 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 メンバーが含まれると、エラーが生成されます。
  • メンバー関数を持つことはできません。

関連項目

クラスと構造体
キーワード
class
struct