union

참고 항목

C++17 이상에서는 std::variantclass가 union에 대해 형식이 안전한 대안이 됩니다.

union은 모든 멤버가 동일한 메모리 위치를 공유하는 사용자 정의 형식입니다. 이 정의는 어느 때라도 union에 멤버 목록의 개체가 둘 이상 포함될 수 없음을 의미합니다. 또한 union은 몇 개의 멤버가 있든 항상 가장 큰 멤버를 저장하는 데 충분한 메모리만 사용한다는 것을 의미합니다.

union은 개체는 많고 메모리는 제한된 경우 메모리를 보존하는 데 유용할 수 있습니다. 하지만 union을 올바르게 사용하려면 주의가 필요합니다. 항상 자신이 할당한 동일한 멤버에 액세스할 수 있도록 해야 합니다. 멤버 형식에 특수한 생성자가 있으면, 코드를 작성하여 해당 멤버를 명시적으로 생성하고 삭제해야 합니다. union을 사용하기 전에 해결하려는 문제를 기본 class와 파생된 class 형식을 사용하여 더 잘 표현할 수 있는지 고려합니다.

구문

union tag opt{ 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으로 불립니다. 이는 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 형식과 함께 비정적 데이터 멤버를 공용 구조체에 포함할 수 있습니다. C++ 11에서는 이러한 제한이 제거됩니다. 이러한 멤버를 union에 포함하면 컴파일러는 사용자 제공이 아닌 특수 멤버 함수를 자동으로 deleted으로 표시합니다. union이 class 또는 struct 내의 익명 union인 경우 사용자 제공이 아닌 class 또는 struct의 특수 멤버 함수는 자동으로 deleted으로 표시됩니다. 다음 예에서는 이 사례를 처리하는 방법을 보여줍니다. union 멤버 중 하나에 이 특별한 처리가 필요한 멤버가 있습니다.

// 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은 참조를 저장할 수 없습니다. 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은 다음 그림과 같이 개념적으로 메모리에 배열됩니다.

NumericType union에서 겹치는 데이터 스토리지를 보여주는 다이어그램.

8바이트 데이터를 보여주는 다이어그램. 이중 형식 dValue는 전체 8바이트를 차지합니다. 긴 lValue 형식은 첫 4바이트를 차지합니다. 짧은 형식의 iValue는 첫 번째 바이트를 차지합니다.

익명 union

익명 union은 class-name 또는 declarator-list없이 선언된 것입니다.

union { member-list }

익명 union에 선언된 이름은 비멤버 변수처럼 직접 사용됩니다. 이는 익명 union에 선언된 이름이 주변 범위에서 고유해야 함을 의미합니다.

익명 union은 다음과 같은 제한 사항이 적용됩니다.

  • 파일 또는 네임스페이스 범위에서 선언된 경우 또한 static으로 선언되어야 합니다.
  • public 멤버만 있을 수 있습니다. 익명 union에 private 멤버와 protected 멤버가 있으면 오류가 발생합니다.
  • 멤버 함수를 가질 수 없습니다.

참고 항목

클래스 및 구조체
키워드
class
struct