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은 한 번에 하나의 비정적 데이터 멤버 만 보유할 수 있는 특별한 클래스 유형입니다.

목차

구문

union 선언을 위한 클래스 지정자는 class 또는 struct 선언과 유사합니다:

union attr class-head-name { member-specification }
attr - (since C++11) 임의 개수의 attributes 선택적 시퀀스
class-head-name - 정의 중인 union의 이름. 선택적으로 nested-name-specifier (이름과 범위 확인 연산자의 시퀀스, 범위 확인 연산자로 끝남)가 앞에 올 수 있음. 이름은 생략 가능하며, 이 경우 union은 무명(unnamed) 이 됨
member-specification - 접근 지정자, 멤버 객체 및 멤버 함수 선언과 정의의 목록

union은 멤버 함수(생성자와 소멸자 포함)를 가질 수 있지만, 가상 함수는 가질 수 없습니다.

union은 기본 클래스를 가질 수 없으며 기본 클래스로 사용될 수 없습니다.

최대 하나의 variant member 만이 default member initializer 를 가질 수 있습니다.

(since C++11)

union은 참조 타입의 비정적 데이터 멤버를 가질 수 없습니다.

유니온은 trivial하지 않은 special member function 을 가진 비정적 데이터 멤버를 포함할 수 없습니다.

(until C++11)

유니온이 trivial하지 않은 special member function 을 가진 비정적 데이터 멤버를 포함하는 경우, 해당 유니온의 특수 멤버 함수가 삭제된 것으로 정의될 수 있습니다. 자세한 내용은 해당 특수 멤버 함수 페이지를 참조하십시오.

(since C++11)

마치 struct 선언에서와 마찬가지로, union에서의 기본 멤버 접근 권한은 public 입니다.

설명

union은 가장 큰 데이터 멤버를 보관하기에 충분한 크기 이상이지만, 일반적으로 그보다 크지는 않습니다. 다른 데이터 멤버들은 가장 큰 멤버의 일부로 동일한 바이트에 할당되도록 의도됩니다. 이 할당의 세부 사항은 구현에 따라 정의되며, 모든 비정적 데이터 멤버가 동일한 주소를 가진다는 점은 예외입니다. 가장 최근에 기록되지 않은 union 멤버에서 읽는 것은 정의되지 않은 동작입니다. 많은 컴파일러들이 비표준 언어 확장으로서 union의 비활성 멤버를 읽을 수 있는 기능을 구현합니다.

#include <cstdint>
#include <iostream>
union S
{
    std::int32_t n;     // 4바이트 차지
    std::uint16_t s[2]; // 4바이트 차지
    std::uint8_t c;     // 1바이트 차지
};                      // 전체 union은 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

각 멤버는 클래스의 유일한 멤버인 것처럼 할당됩니다.

사용자 정의 생성자와 소멸자를 가진 클래스들이 union의 멤버인 경우, 활성 멤버를 전환하려면 일반적으로 명시적 소멸자 호출과 배치 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부터)

두 개의 공용체 멤버가 standard-layout 타입인 경우, 어떤 컴파일러에서든 그들의 공통 부분 시퀀스를 검사하는 것은 잘 정의되어 있습니다.

멤버 수명

수명 이 시작되는 것은 해당 멤버가 활성화될 때입니다. 이전에 다른 멤버가 활성 상태였다면, 그 멤버의 수명은 종료됩니다.

유니온의 활성 멤버가 내장 대입 연산자나 trivial 대입 연산자를 사용하는 E1 = E2 형식의 대입 표현식으로 전환될 때, trivial하거나 삭제된 기본 생성자를 가지지 않는 클래스가 아닌 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;               // 어떤 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의 수명이 암시적으로 시작되지 않음
}

Trivial move constructor, move assignment operator, (since C++11) copy constructor 및 copy assignment operator는 union 타입의 객체 표현을 복사합니다. 소스와 대상이 동일한 객체가 아닌 경우, 이러한 특수 멤버 함수들은 복사 수행 전에 소스에 중첩된 객체에 대응하는 대상에 중첩된 모든 객체(대상의 서브객체도 아니고 implicit-lifetime type 도 아닌 객체 제외)의 수명을 시작합니다. 그렇지 않은 경우, 아무 작업도 수행하지 않습니다. 두 union 객체는 trivial 특수 함수를 통한 생성 또는 할당 후에 동일한 대응 활성 멤버(존재하는 경우)를 갖습니다.

익명 공용체

익명 공용체 는 변수(공용체 타입의 객체, 참조자, 공용체에 대한 포인터를 포함)를 동시에 정의하지 않는 이름 없는 공용체 정의입니다.

union { 멤버 명세 } ;

익명 공용체는 추가적인 제한 사항이 있습니다: 멤버 함수를 가질 수 없으며, 정적 데이터 멤버를 가질 수 없고, 모든 데이터 멤버는 공개되어야 합니다. 허용되는 선언은 비정적 데이터 멤버 static_assert 선언 (C++11부터) 만 가능합니다.

익명 공용체의 멤버들은 둘러싸는 범위에 주입되며(그리고 그곳에 선언된 다른 이름들과 충돌해서는 안 됩니다).

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

네임스페이스 범위의 익명 공용체는 이름 없는 네임스페이스에 나타나지 않는 한 static 으로 선언되어야 합니다.

유니언 형태의 클래스

유니언과 유사한 클래스 는 유니언이거나, (비유니언) 클래스로서 적어도 하나의 익명 유니언을 멤버로 가지는 클래스입니다. 유니언과 유사한 클래스는 variant 멤버 의 집합을 가집니다:

  • 해당 멤버 익명 공용체의 비정적 데이터 멤버;
  • 추가로, 공용체와 유사한 클래스가 공용체인 경우, 익명 공용체가 아닌 비정적 데이터 멤버.

유니온과 유사한 클래스들은 태그드 유니온(tagged union) 을 구현하는 데 사용될 수 있습니다.

#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 Unions [class.union]
  • C++20 표준 (ISO/IEC 14882:2020):
  • 11.5 Unions [class.union]
  • C++17 표준 (ISO/IEC 14882:2017):
  • 12.3 Unions [class.union]
  • C++14 표준 (ISO/IEC 14882:2014):
  • 9.5 Unions [class.union]
  • C++11 표준 (ISO/IEC 14882:2011):
  • 9.5 Unions [class.union]
  • C++03 표준(ISO/IEC 14882:2003):
  • 9.5 Unions [class.union]
  • C++98 표준 (ISO/IEC 14882:1998):
  • 9.5 Unions [class.union]

참고 항목

(C++17)
타입 안전 식별 공용체
(클래스 템플릿)
C 문서 for Union 선언