enum 정의를 C++11의 enum class로 바꿀 때 유의할 사항
기존 enum의 명확한 단점은 다음의 소스 코드로 알 수 있습니다.
enum TestMode
{
OFF,
};
enum MyMode
{
OFF, // Error C2365 'OFF': redefinition; previous definition was 'enumerator'
};
이 때문에, 어쩔 수 없이 Prefix를 정의해 함께 사용해야만 했습니다.
enum TestMode
{
TM_OFF,
};
enum MyMode
{
MM_OFF,
};
윈도우 헤더 파일에 정의된 수많은 enum 타입들의 상수가 왜 타입명을 함께 붙이고 정의하는지,
// windef.h
typedef enum DPI_AWARENESS {
DPI_AWARENESS_INVALID = -1,
DPI_AWARENESS_UNAWARE = 0,
DPI_AWARENESS_SYSTEM_AWARE = 1,
DPI_AWARENESS_PER_MONITOR_AWARE = 2
} DPI_AWARENESS;
바로 이런 이유 때문이었던 것입니다.
그러다가, C++11 표준에서 "enum class"가 나와 더 이상 접미사가 필요 없게 되었습니다.
enum class TestMode
{
ON,
OFF,
};
enum class MyMode
{
ON,
OFF
};
int main()
{
MyMode mode1 = MyMode::ON;
MyMode mode2 = ON; // 컴파일 오류
}
하지만, 그래도 내부적으로는 "#define"과의 충돌을 해결하지 못해 다음과 같은 상황에서는 컴파일 오류가 발생합니다.
#define ON 100
enum class MyMode
{
ON, // Error C2059 syntax error: 'constant'
OFF
};
물론 이해는 됩니다. "ON"이라는 리터럴 자체를 #define 정의에 의해 모조리 치환하기 때문에 결국 다음과 같이 번역되므로 오류가 발생하는 것이 당연합니다.
#define ON 100
enum class MyMode
{
100, // Error C2059 syntax error: 'constant'
OFF
};
따라서 기존 헤더 파일에 같은 상수명이 정의되어 있다면 접미사를 붙이거나 명시적으로 #undef으로 해결할 수밖에 없습니다.
enum class TestMode
{
TM_ON,
OFF,
};
enum class MyMode
{
#undef ON
ON,
OFF
};
enum class의 또 하나 문제점(?)이 있다면, 형식 안정성으로 인해 기존 enum이 int 타입 연산을 자연스럽게 할 수 있었던 것을 못하게 막는다는 점입니다. 즉, 다음과 같은 식의 비트 플래그 연산들이 enum class 사용 후부터 모두 오류가 발생하게 됩니다.
int main()
{
MyMode mode1 = MyMode::ON;
MyMode mode2 = MyMode::OFF;
// Error C2676 binary '&': 'MyMode' does not define this operator or a conversion to a type acceptable to the predefined operator
if ((mode1 & mode2) == mode2)
{
}
}
이 문제를 해결하려면 명시적인 int 형변환을 하거나,
if (((int)mode1 & (int)mode2) == (int)mode2)
"(int)" 형변환 따위의 코드 수정 없이 하고 싶다면, 좀 더 우아하게는 다음과 같이 연산자 재정의를 추가하면 됩니다.
inline MyMode operator&(MyMode& l, MyMode &r)
{
return (MyMode)((int)l & (int)r);
}
inline MyMode operator|(MyMode& l, MyMode& r)
{
return (MyMode)((int)l | (int)r);
}
멋스럽게 & 연산자를 추가해봤지만 저런 경우 다음과 같은 식의 상황에서는 컴파일 오류가 발생하므로,
auto GetFlags() -> MyMode
{
return mode;
}
int main()
{
MyMode mode = MyMode::ON;
MyMode mode2 = MyMode::OFF;
if ((mode | mode2) == mode2)
{
mode = MyMode::ON;
}
char* ptr = nullptr;
// Error C2678 binary '&': no operator found which takes a left-hand operand of type 'MyMode' (or there is no acceptable conversion)
if ((GetFlags() & mode2) == mode2)
{
mode = MyMode::ON;
}
}
이 상황을 해결하기 위해 const 등의 좀 더 복잡한 코드를 만들 수도 있지만,
auto GetFlags() -> const MyMode&
{
return mode;
}
inline MyMode operator&(const MyMode& l, MyMode &r)
{
return (MyMode)((int)l & (int)r);
}
어차피 enum이 int 범위 내의 값이라는 특성을 감안하면 굳이 const로 만들지 않아도 된다는 점과, 오히려 ref를 받아 변경할 수 있도록 하는 것이 아니라면 애당초 그냥 아무 처리 없이 사용하는 것이 더 나은 선택일 것입니다.
inline MyMode operator&(MyMode l, MyMode r)
{
return (MyMode)((int)l & (int)r);
}
inline MyMode operator|(MyMode l, MyMode r)
{
return (MyMode)((int)l | (int)r);
}
(
첨부 파일은 이 글의 예제 코드를 포함합니다.)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]