범위 없는( unscoped ) enum
C++에서 보통의 식별자들의 경우, 중괄호 블록을 벗어나게 되면, 효력을 잃게 마련입니다.
int main(){
{
int val = 0;
}
val += 10; // error ! 변수의 범위를 벗어났음
}
그렇지만, 아래의 enum 타입의 식별자 같은 경우, 이러한 제약에 영향을 받지 않습니다.
int main(){
enum Color{ white, black, red };
auto white = false; // error !
enum Color2{ black, yellow, blue }; // error !! black 식별자 중복
}
▼출력
error: 'auto white' redeclared as different kind of symbol
위의 white
열거자( enumerator )는 Color
타입의 범위를 넘어서도 유효하기 때문에, 그 아래의 white
변수와 이름이 충돌됩니다.
심지어, 중괄호 블록에 의해 완전히 분리되었다고 생각되는 Color2
의 열거자들과도 이름 충돌이 발생합니다.
이와 같은 일이 발생하는 이유는 위 Color
같은 enum 타입이 범위( scope )를 제공하지 않기 때문입니다.
이러한 enum을 범위 없는( unscoped ) enum이라고 부릅니다.
[C++] 몇 가지 값만 가질 수 있는 타입: unscoped enum
두 가지 값만 가질 수 있는 타입 boolbool 타입은 C++의 기본적인 타입( fundamental type )의 하나입니다.이 타입은 키워드로 정해진 두 값 true와 false만을 가질 수 있는데, 이 상징적인 상수 true는 사실
codingbonfire.tistory.com
범위 없는 enum의 단점
범위 없는 enum 타입의 범위를 제공하지 않는 특성 때문에, 위에서 예를 들었던 이름 충돌 문제를 해결하려면, enum의 열거자에 특별한 접두사를 붙인다던가 namespace를 사용해야 불편함이 있습니다.
enum Color1{
white,
black,
red,
};
enum Color2{
cr_white, // 열거자에 접두어 사용
cr_black,
cr_red,
};
namespace Namescope{ // namespace 사용
enum Color3{
white,
black,
red,
};
}
int main(){
int var1{ white };
int var2{ cr_white };
int var3{ Namescope::white };
}
위의 white
와 Namescope::white
는 다른 이름으로 처리되고, 이것이 namespace를 사용하는 이유입니다.
[C++] 이름 충돌을 해결하기 위한 namespace
namespacenamespace는, 단어에서 알 수 있듯이, 이름( identifier )이 유일하게 정의된 공간을 말합니다.그리고, 이러한 공간을 만드는 이유는, 생각 외로 자주 발생하는 이름의 중복을 막기 위해서입니다
codingbonfire.tistory.com
그리고, 이 기능은 외부의 라이브러리가 범위 없는 enum 타입을 사용할 때 특히 유용합니다.
● 게다가, 범위 없는 enum 타입의 열거자들은 암시적으로 정수 타입( integral type )으로 변환됩니다.
그런데, 정수 타입은 필요한 경우 다시 double
타입으로 변환될 수 있으므로, 다음의 코드도 아무런 무리 없이 컴파일됩니다.
int main(){
enum Color{ white, black, red };
int result = 0;
if ( black > 3.5){ // enum 타입과 double 타입을 비교 ??
result = 1;
}
return result;
}
그래서, 위와 같이 black
을 3.5
와 비교하는 이상한 코드를 만들 수도 있습니다.
● 그리고, 범위 없는 enum은 전방 선언( forward declaration )을 할 수 없습니다.
왜냐하면, ( 범위 없는 enum의 전방 선언에는 아직 열거자들을 명시하지 않았으므로 ) 컴파일러가 enum의 열거자들을 모두 담을 수 있는 타입을 정할 수 없기 때문입니다.
따라서, 다음과 같은 코드는 오류입니다.
enum Color; // 전방 선언. error !!
void UseEnum( Color e);
enum Color{
white = 100,
black = 100000000,
red,
};
▼출력
error: use of enum 'Color' without previous declaration
위에서, enum Color
의 모든 열거자들( enumerators )의 타입은 적어도 값 100,000,000
을 담을 수 있는 타입이어야 합니다. ( ex. int 타입 )
그렇지만, 전방 선언만을 보고선, 이 타입의 열거자가 어떤 값을 갖는지는 알 수 없고, 이렇게 열거자의 구체적인 타입을 확정할 수 없으면 컴파일할 수 없습니다.
● 참고로, 전방 선언( forward declaration )이란 식별자를 정의하기 전에, 식별자의 존재를 컴파일러에 미리 알리는 것입니다.
이렇게 함으로써, 컴파일 시간을 대폭 줄일 수 있게 됩니다.
[C++] 식별자를 알려주는 전방 선언( forward declaration )
전방 선언( forward declaration )식별자( identifier )란 변수, 함수, 타입 등의 이름을 말합니다.int a = 0; // a: 변수 식별자int add( int, int ); // add: 함수 식별자class CSomething; // CSomething: 타입 식별자 이러한
codingbonfire.tistory.com
예를 들면, 함수 선언은 헤더 파일에서 하고( 이것이 전방 선언 ), 함수는 구현은 다른 파일에 둡니다.( cpp 같은 )
그러면, 이 함수를 사용하는 프로그램의 다른 코드들은, 헤더 파일을 포함함으로써, 함수의 구현과 무관하게 함수를 사용할 수 있을 뿐 아니라, 함수의 구현 부분을 수정하더라도, 재컴파일 할 필요가 없게 됩니다.
범위 있는( scoped ) enum
위에서 나열한 범위 없는( unscoped ) enum의 문제점들을 해결하기 위해 도입된 것이 바로 범위 있는( scoped ) enum 구문입니다.
이 범위 있는 enum은 enum 뒤에 class 키워드를 사용해서 선언하게 됩니다.
int main(){
enum class Color1{ white, black, red }; // 범위 있는 enum
enum class Color2{ white, yellow, blue };
}
이 enum에는 범위가 적용되므로, Color1
의 white
와 Color2
의 white
는 구분이 됩니다.
그리고, 이러한 범위 있는 enum의 열거자( enumerator )에 접근하려면 아래와 같이 해야 합니다.
int main(){
enum class Color{ white, black, red };
Color c = Color::red; // red 열거자에 접근
}
위처럼, Color
타입의 red
열거자에 접근하려면, enum 타입의 이름 Color
와 범위 지정 연산자( scope resolution operator ) ::
를 같이 사용해야 합니다.
● 그리고, 이 범위 있는 enum 타입은, 범위 없는 enum 타입과 달리, 다른 타입으로의 암시적인 변환을 할 수 없습니다.
따라서, 다음의 코드는 오류입니다.
int main(){
enum class Color{ white = 100, black, red }; // 범위 있는 enum
int result = 0;
if ( Color::black > 3.5){ // 암시적인 변환 금지. error !!
result = 1;
}
return result;
}
위의 Color::black
열거자를 double
타입으로 변환할 수 없기 때문에, >
연산자의 인수가 될 수 없으므로 오류입니다.
만약, 이 enum 타입의 변환이 필요하다면, 명시적인 변환을 거쳐서 위의 비교 구문을 수행할 수는 있습니다.
int main(){
enum class Color{ white = 100, black, red }; // 범위 있는 enum
int result = 0;
if ( static_cast<double>( Color::black ) > 3.5){ // 명시적인 변환
result = 1;
}
return result;
}
● 그리고, 범위 있는 enum은 전방 선언( forward declaration )이 가능합니다.
이것은 가능한 이유는, 이 enum의, 바탕 타입( underlying type )이라는 하는, 기본 타입이 int
로 정해져 있기 때문입니다.
그래서, 범위 없는 enum과 달리 다음 코드가 가능합니다.
enum class Color; // 전방 선언. ok
void UseEnum( Color e);
컴파일러는 위의 UseEnum
함수의 매개변수 e
의 타입이 int
타입이라는 것을 알고 있으므로, 함수를 컴파일하는데 아무런 문제가 없습니다.
게다가, 원하는 경우( 타입의 크기가 중요시되는 프로그램의 경우 ) enum의 바탕 타입을 변경할 수도 있습니다.
enum class Color: std::uint16_t // 바탕 타입 변경
{
white,
black,
red,
};
enum Unscoped_Color: uint8_t // 범위 없는 enum의 바탕 타입
{
white,
black,
red,
}
그리고, 범위 있는 enum 도입과 동시에, 위와 같이 기존의 범위 없는 enum도 바탕 타입을 설정할 수 있게 되었습니다.
예전의 범위 없는 enum의 전방 선언이 불가능했던 것은, 이 타입의 열거자의 타입을 설정할 수 없었기 때문이므로, 이젠 범위 없는 enum도 전방 선언이 가능하게 되었습니다.
enum Color : int; // 범위없는 enum의 전방 선언. ok
void UseEnum( Color e);
위에서 Color
뒤의 : int
를 빼먹으면, 이전과 마찬가지로 컴파일이 안됩니다.
정리
- 범위 있는( scoped ) enum 타입은 범위를 제공하는 enum 타입입니다. 그렇기 때문에, 이 타입의 열거자( enumerator )에 접근하려면, 이 타입의 이름과 범위 지정 연산자 ::를 사용해야 합니다.
- 범위 있는 타입은 암시적으로 다른 타입으로 변환되지 않습니다.
- 범위 있는 타입의 열거자의 바탕 타입( underlying type )은 기본적으로 int 타입입니다. 하지만 원하는 경우 다른 타입으로 변경할 수 있습니다.
이 글과 관련 있는 글들
C-style 타입 변환과 C++의 static_cast