열거형은 사용자가 지정한 정수 값 집합을 보유할 수 있는 유형이다. 열거형의 가능한 값 중 일부는 열거자(enumerator)로 명명되고 호출된다.
enum class Color { red, green, blue };
이것은 열거자가 red, green, blue인 Color라는 열거를 정의한다. enumberation은 enum으로 축약된다.
열거형에는 두 가지 종류가 있다.
- enum class : 열거자의 이름 (예:red)이 열거형에 대해 local이고 해당 값이 다른 유형으로 암시적으로 변환되지 않는 경우.
- plain enum : 열거자 이름이 열거형과 동일한 범위에 있고 해당 값이 암시적으로 정수로 변환되는 경우
일반적으로 enum class는 fewer suprises 하기 때문에 선호하는 편이다.
enum class
enum class는 범위가 지정되고 강력한 형식의 열거형이다.
enum class Traffic_light { red, yellow, green };
enum class Warning { green, yellow, orange, red };// fire alert lev els
Warning a1 = 7; // error : no int->War ning conversion
int a2 = green; // error : green not in scope
int a3 = Warning::green; // error : no War ning->int conversion
Warning a4 = Warning::green; // OK
void f(Traffic_light x)
{
if (x == 9) { /* ... */ } // error : 9 is not a Traffic_light
if (x == red) { /* ... */ } // error : no red in scope
if (x == Warning::red) { /* ... */ } // error : x is not a War ning
if (x == Traffic_light::red) { /* ... */ } // OK
}
두 열거형에 있는 열거자는 각각 고유한 enum class의 범위에 있기 때문에 충돌하지 않는다. 열거형은 일부 정수 유형으로 표시되고 각 열거자는 일부 정수 값으로 표시된다. 열거형을 나타내는 데 사용되는 형식이 바탕 형식(underlying type)이다. 바탕 형식은 signed or unsigned integer 형식 중 하나여야 한다. 기본 값은 int다.
enum class Warning : int { green, yellow, orange, red }; // sizeof(War ning)==sizeof(int)
공간이 너무 낭비라고 생각되면 char를 사용할 수 있다.
enum class Warning : char { green, yellow, orange, red }; // sizeof(War ning)==1
기본적으로 열거자 값은 0부터 증가하여 할당된다.
static_cast<int>(Warning::green)==0
static_cast<int>(Warning::yellow)==1
static_cast<int>(Warning::orange)==2
static_cast<int>(Warning::red)==3
일반 int 대신 Warning 변수를 선언하면 사용자와 컴파일러 모두에게 의도된 용도에 대한 힌트를 줄 수 있다.
void f(Warning key)
{
switch (key) {
case Warning::green:
// do something
break;
case Warning::orange:
// do something
break;
case Warning::red:
// do something
break;
}
}
사람은 yellow가 누락되었음을 알아차릴 수 있으며 경고 값 4개 중 3개만 처리되기 때문에 컴파일러에서 경고를 발생시킬 수 있다. 열거자는 정수 형식의 상수 식으로 초기화할 수 있다.
enum class Printer_flags {
acknowledge=1,
paper_empty=2,
busy=4,
out_of_black=8,
out_of_color=16,
//
};
Printer_flags 열거자의 값은 비트 연산으로 결합될 수 있도록 선택된다. 열거형은 사용자 정의 유형으로 | 및 & 연산자.
constexpr Printer_flags operator|(Printer_flags a, Printer_flags b)
{
return static_cast<Printer_flags>(static_cast<int>(a))|static_cast<int>(b));
}
constexpr Printer_flags operator&(Printer_flags a, Printer_flags b)
{
return static_cast<Printer_flags>(static_cast<int>(a))&static_cast<int>(b));
}
class enum은 암시적 변환을 지원하지 않기 때문에 명시적 변환이 필요하다. 이러한 정의를 감안할 때 | 와 & Printer_flags의 경우 아래와 같이 쓸 수 있다.
void try_to_print(Printer_flags x)
{
if (x&Printer_flags::acknowledge) {
// ...
}
else if (x&Printer_flags::busy) {
// ...
}
else if (x&(Printer_flags::out_of_black|Printer_flags::out_of_color)) {
// either we are out of black or we are out of color
// ...
}
// ...
}
operator|() 및 operator&()를 constexpr 함수로 정의했는데, 누군가가 상수 표현식에 이러한 연산자를 사용하기를 원할 수 있기 때문이다.
void g(Printer_flags x)
{
switch (x) {
case Printer_flags::acknowledge:
// ...
break;
case Printer_flags::busy:
// ...
break;
case Printer_flags::out_of_black:
// ...
break;
case Printer_flags::out_of_color:
// ...
break;
case Printer_flags::out_of_black&Printer_flags::out_of_color:
// we are out of black *and* out of color
// ...
break;
}
// ...
}
enum class는 정의하지 않고 선언하는 것이 가능하다.
enum class Color_code : char; // declaration
void foobar(Color_code∗ p); // use of declaration
// ...
enum class Color_code : char { // definition
red, yellow, green, blue
};
정수 형식의 값은 열거형 형식으로 명시적으로 변환될 수 있다. 값이 열거형의 기본 유형 범위 내에 있지 않는 한 이러한 변환의 결과는 정의되지 않는다.
enum class Flag : char{ x=1, y=2, z=4, e=8 };
Flag f0 {}; // f0 gets the default value 0
Flag f1 = 5; // type error: 5 is not of type Flag
Flag f2 = Flag{5}; // error : no narrowing conversion to an enum class
Flag f3 = static_cast<Flag>(5); // brute force
Flag f4 = static_cast<Flag>(999); // error : 999 is not a char value (maybe not caught)
마지막 할당은 정수에서 열거형으로 암시적 변환이 없는 없는 이유를 보여준다. 대부분의 정수 값은 특정 열거형에 표현이 없다. 각 열거자는 정수 값을 갖는다. 그 값은 명시적으로 추출할 수 있다.
int i = static_cast<int>(Flag::y); // i becomes 2
char c = static_cast<char>(Flag::e); // c becomes 8
열거형에 대한 값 범위의 개념은 파스칼 언어 계열의 열거형 개념과 다른다. 그러나 열거자 집합 외부의 값을 잘 정의해야 하는 비트 조작 예저(Printer_flags)는 C 및 C++에서 오랜 역사를 가지고 있다. enum class의 sizeof는 기본 유형의 크기다. 특히 기본 유형이 명시적으로 지정되지 않은 경우 크기는 sizeof(int)다.
일반 열거형
"일반 열거형"은 enum class가 도입되기 전에 C++에서 제공한 대략적인 것이므로 C 및 C++98 스타일 코드에서는 찾을 수 없다. 일반 열거형의 열거자는 열거형 범위로 내보내 지며 암시적으로 일부 정수 유형의 값으로 변환된다.
enum Traffic_light { red, yellow, green };
enum Warning { green, yellow, orange, red }; // fire alert lev els
// error : two definitions of yellow (to the same value)
// error : two definitions of red (to different values)
Warning a1 = 7; // error : no int->War ning conversion
int a2 = green; // OK: green is in scope and converts to int
int a3 = Warning::green; // OK: War ning->int conversion
Warning a4 = Warning::green; // OK
void f(Traffic_light x)
{
if (x == 9) { /* ... */ } // OK (but Traffic_light doesn’t have a 9)
if (x == red) { /* ... */ } // error : two reds in scope
if (x == Warning::red) { /* ... */ } // OK (Ouch!)
if (x == Traffic_light::red) { /* ... */ } // OK
}
단일 범위에서 두 개의 일반 연거로 red를 정의하여 찾기 어려운 오류를 찾을 수 있는 것은 "운"이다. 열거자를 명확하게 하여 일반 열거형을 "정리"하는 것을 고려해야 한다. (작은 프로그램에서는 쉽게 수행되지만 큰 프로그램에서는 큰 어려움이 있을 수 있음).
enum Traffic_light { tl_red, tl_yellow, tl_green };
enum Warning { green, yellow, orange, red }; // fire alert lev els
void f(Traffic_light x)
{
if (x == red) { /* ... */ } // OK (ouch!)
if (x == Warning::red) { /* ... */ } // OK (ouch!)
if (x == Traffic_light::red) { /* ... */ } // error : red is not a Traffic_light value
}
컴파일러는 x==red를 받아들이는데, 이는 거의 확실한 버그다. 포함하는 범위에 이름을 삽입하는 것 (enum, enum class나 class가 아님)은 네임스페이스 오염이며 더 큰 프로그램에서 주요 문제가 될 수 있다.
enum class와 마찬가지로 일반 열거형의 기본 유형을 지정할 수 있다. 그렇게 하면 정의하지 않고 열거형을 선언할 수 있다.
enum Traffic_light : char { tl_red, tl_yellow, tl_green }; // underlying type is char
enum Color_code : char; // declaration
void foobar(Color_code∗ p); // use of declaration
// ...
enum Color_code : char { red, yellow, green, blue }; // definition
기본 유형을 지정하지 않으면 정의하지 않고 열거형을 선언할 수 없으며 기본 유형은 비교적 복잡한 알고리즘에 의해 결정된다. 모든 열거자가 음수가 아닌 경우 열거 범위는 [0:2k-1], 여기서 2k는 모든 열거자가 범위 내에 있는 2의 가장 작은 거듭제곱이다. 음수 열거자가 있는 경우 범위는 [-2k:2k-1]이다. 이것은 기존의 2의 보수 표현을 사용하여 열거자 값을 보유할 수 있는 가장 작은 비트 필드를 정의한다.
enum E1 { dark, light }; // range 0:1
enum E2 { a = 3, b = 9 }; // range 0:15
enum E3 { min = −10, max = 1000000 }; // range -1048576:1048575
정수를 일반 열거형으로 명시적으로 변환하는 규칙은 명시적 기본 유형이 없는 경우 값이 열거형 범위 내에 있지 않으면 이러한 변환의 결과가 정의되지 않는다는 점을 제외하고는 enum class의 경우와 동일하다.
enum Flag { x=1, y=2, z=4, e=8 }; // range 0:15
Flag f0 {}; // f0 gets the default value 0
Flag f1 = 5; // type error: 5 is not of type Flag
Flag f2 = Flag{5}; // error : no explicit conversion from int to Flag
Flag f2 = static_cast<Flag>(5); // OK: 5 is within the range of Flag
Flag f3 = static_cast<Flag>(z|e); // OK: 12 is within the range of Flag
Flag f4 = static_cast<Flag>(99); // undefined: 99 is not within the range of Flag
일반 열거형에서 기본 유형으로 암시적 변환이 있기 때문에 | 이 예제가 작동하도록 하려면 z|e를 평가할 수 있도록 z와 e를 int로 변환한다. 열거형의 sizeof는 기본 유형의 sizeof이다. 기본 유형이 명시적으로 지정되지 않은 경우 열거자가 int 또는 unsigned int로 표시될 수 없는 경우가 아니면 해당 범위를 보유할 수 있고 sizeof(int) 보다 크지 않은 일부 정수 유형이다. 예를 들어 sizeof(e1)는 1 또는 4 일 수 있지만 sizeof(int)==4 인 시스템에서는 8이 아니다.
일반 열거형은 이름을 정하지 않을 수 있다.
enum { arrow_up=1, arrow_down, arrow_sideways };
변수를 사용할 유형이 아니라 정수 상수 집합만 필요할 때 사용한다.
'Program Language > C & C++' 카테고리의 다른 글
[C++] range-for 문 (0) | 2022.01.05 |
---|---|
[C++] 선언 명령문 (0) | 2022.01.05 |
[C++] Unions (0) | 2022.01.04 |
[C++] 구조체의 필드(field) (0) | 2022.01.04 |
[C++] 구조체의 POD(plain old data) (0) | 2022.01.04 |