[출처] [C/C++] enum, 보다 나은 enum|작성자 esstory
#define 과 얼핏 비슷해 보이지만, 비슷한 유형별의 데이터를 표현하기 위해 반드시 필요한 C++의 필수 타입인 enum 의 몇 가지 재미있고 유용한 팁을 소개합니다.
1. namespace 와 결합하기
일반적으로 enum 을 이용하여 타입이름을 짓기란 쉬운 일이 아닙니다.
만약 리스트에 필요한 정렬 기준을 enum 으로 표현한다면 아래 정도가 됩니다.
enum SORT_LIST
{
SORT_DATE, // 날짜순
SORT_NAME, // 이름
SORT_CONTENT, // 내용
S0RT_ETC, // 기타
};
bool SortSomeData(SORT_LIST SortList)
{
switch (SortList)
{
case SORT_DATE:
break;
case S0RT_NAME:
break;
// ....
}
}
하지만, 이러한 enum 표현 방식은
- SORT_DATE 라는 이름은 너무 흔합니다. 프로그램이 조금만 커져도 이러한 이름들이 다른 곳에서도 필요하게 되어 이름 충돌을 피하기 힘들어 집니다. 이름 충돌을 피하기 위해서는 SORT_LIST를 변경해도 아무런 소용이 없고 SORT_DATE 를 완전히 다른 이름으로 지어야만 합니다.
- enum 의 타입이름인 SORT_LIST와 실제 값인 SORT_DATE 를 서로 연관 짓기가 힘듭니다. switch 문 내에서도 타입이름(SORT_LIST)은 사용되지 않아서 SORT_DATE만 보고 이 값이 SORT_LIST 의 한 유형인지 구분 짓기 힘듭니다
- enum 값에 항상 들어 있는 SORT 라는 prefix 도 부담스럽습니다. SORT_XXX 식으로 이름을 짓다 보니, 타입핑하기도 힘들구요
평소 이런 문제에 대해서 알고는 있었지만 그 해결책을 고민만 하던 차에 아주 좋은 해결책을 발견했습니다. 바로 아래 글에서요. (늘 해결책은 제가 아닌 책이나 다른 글에서 구합니다.)
Stupid C++ Tricks #2: Better Enums
이 글에서 제안하는 방식은 enum 을 namespace과 결합하여 가독성을 증가 시키고, 이름충돌도 최소화시키는 방식입니다. 위에 든 예를 namespace 식으로 표현하면 다음과 같습니다.
namespace SortList
{
enum Enum
{
BytDate, // 다가올날기준
ByName, // 이름
ByContent, // 내용
Etc,
};
}
bool SortSomeData(SortList::Enum SortList)
{
switch (SortList)
{
case SortList::ByDate:
break;
case SortList::ByName:
break;
// ....
}
}
namespace 와 결합함으로써, enum 의 각 항목에는 항상 SortList 라는 namespace가 따라 붙게 되어 가독성이 증가함을 알 수 있습니다. SORT_DATE 보다는 SortList::ByDate 가 읽기 쉽고, 요즘 웬만한 IDE 에서는 namespace인 SortList::만 입력하면 자동으로 이하 소속되는 타입이름들이 나열되기 때문에 미스타이핑 할 가능성도 줄여주네요.
게다가 namespace 를 사용하기 때문에 다른 모듈과 이름이 충돌할 가능성도 줄어들었습니다.
이름 충돌이 없기 때문에 아래와 같이 NameEdit 라는 enum 값을 여러 namespace 에 걸쳐 사용하면서 각각의 의미에 맞는 값들을 줄 수가 있게 됩니다.
namespace ControlWidth
{
enum Enum
{
NameEdit = 20,
AddressCombo = 60,
OkBtn = 30;
// ....
};
};
namespace ControlID
{
enum Enum
{
NameEdit = 100,
AddressCombo = 101,
OkBtn = 102;
// ....
};
}
이와 같이 enum 을 표시하는 방식은 실제 꽤 유용하게 써 먹을 수 있어서 지금 새로 만들고 있는 프로젝트에서도 재작성하는 코드에서 enum 타입을 위 방식으로 변경하고 있는 중입니다.
괜찮은 방법 아닌가요 ? ^^;
2. enum 에 문자열 결합하기
2번째 팁은 숫자로만 되어 있는 enum 을 문자열로 치환하는 팁입니다.
C++ 에서 enum 을 정수형으로만 치환가능하기 때문에 사용자가 읽을 수 있는 텍스트로 변경하기 위해서는 enum 값에 1:1 매칭되는 텍스트가 필요한 경우가 많습니다.
이를 해결하는 방법으로 아래 포스팅에서는 enum 과 #define 매크로를 이용해서 문자열을 매칭시키는 방법을 소개합니다.
Enums, Macros, Unicode and Token-Pasting
Visual C++ Team Blog 에 소개된 이 방법은 숫자로이뤄진 enum 값과
문자로이뤄진char* 값을매칭시키는방안에대한내용입니다.
이를 위해 animal.inc 파일에 아래 라인들을 추가하고
MYENUM(dog)
MYENUM(cat)
MYENUM(fish)
MYENUM(bird)
아래와 같이 enum 을 선언합니다.
enum Animal {
#define MYENUM(e) _##e,
#include "animal.inc"
#undef MYENUM
};
wchar_t* AnimalDescription[] = {
#define MYENUM(e) L"_" L#e,
#include "animal.inc"
#undef MYENUM
};
#define 으로 조금 복잡해 보여서 그렇지, enum 값과 wchar_t* 배열의 선언을 MYENUM 매크로 정의를 통해 확장하는 식입니다. 조금 특이한 것은 MYENUM 을 enum 과 wchar_t* 배열 속에서 조금씩 다르게 선언하고, 데이터를 추가한 후( #include "animal.inc") 에는 바로 undefined 시켜버리는 정도네요
이 방법 마음에 드시나요?
해당 링크에 달린 댓글들을 보면 많은 분들이 마음에 들지 않나 봅니다.
가장 크게 문제 삼은 내용은 역시 아래 글과 같은 #define 매크로에 대한 조금 헤묵은 논쟁들입니다.
“Macros is something C++ (especially the Standards Committee) has always tried to avoid. Such use of macros like this will make code unreadable and cause maintainance problems. I think it's not a good idea to play with such thing.”
개인적으로는 #define 을 활용한 매크로 활용을 좋게 보고 있습니다.
제가 #define을 좋아하는 이유는
- 코딩량을 줄여 줍니다. 코딩하는 량이 줄어든다는 것은 그만큼 유지관리할 코드가 줄어 들고, 미스타이핑에 의한 작은 실수들을 걸러주기 때문입니다.
- 자주 해야 하지만 귀찮은 작업들 - Get/Set 함수 같은 - 을 쉽게 처리해 줍니다. 저 같은 경우 클래스 하나를 만들면 대부분은 private 멤버변수들이어서 늘 public 으로 Get/Set 함수를 구현하는 게 고역 중에 고역인데요. #define 매크로를 활용하면 늘 심플하고 코드 보기도 편하더군요.
Visual C++ 에 있는 _countof 매크로도 정말 없어서는 안될 매크로 중 하나입니다.
extern "C++"
{
template <typename _CountofType, size_t _SizeOfArray>
char (*__countof_helper(UNALIGNED _CountofType (&_Array)[_SizeOfArray]))[_SizeOfArray];
#define _countof(_Array) sizeof(*__countof_helper(_Array))
}
아무튼, 논란의 여지가 있긴 하지만, 이 팁의 주요 목적은 enum Type 으로 추가할 숫자값과, 문자열 값이 어느 하나만 추가되거나, 삭제되지 않고 늘 한번에 관리 될 수 있도록 하기 위한 방안 중에 하나라고 봅니다. 유용했다면 쓰면 될 것이고 그렇지 않으면 안 쓰면 되겠죠 :-)
댓글 중에는 하나의 .inc 를 include 할 필요 없이 하나의 파일에서 모두 해결하는 방안도 있으니 참고하세요
#define ANIMALS(FOO) \
FOO(dog) \
FOO(fish) \
FOO(cat)
#define DO_DESCRIPTION(e) L"_" L#e,
#define DO_ENUM(e) _##e,
wchar_t* AnimalDescription[] = {
ANIMALS(DO_DESCRIPTION)
};
enum Animal {
ANIMALS(DO_ENUM)
};
1, 2번에 비하면 좀 약한 팁입니다만, enum 을 사용할 때 처음과 끝을 지정하자는 아이디어는 “Code Complete 2” 책에 p436 에 나와있습니다.
아래와 같은 국가명을 열거할 필요가 있을 때, 처음과 끝에 해당하는 값을 XXX_FIRST 와 XXX_LAST로 지정하는 방법입니다.
enum Country
{
Country_First = 0,
Country_China = 0,
Country_England = 1,
Country_France = 2,
Country_Germany = 3,
Country_India = 4,
Country_Korea = 5,
Country_Japan = 6,
Country_Usa = 7,
Country_Last = 7,
};
XXX_FIRST 와 XXX_LAST 가 시작과 끝을 가르키기 때문에 이를 활용하여 열거형의 모든 항목을 조회하여 원하는 일을 할 때 유용합니다.
for (Country cty = Country_First; cty <= Country_Last; cty++)
{
// 원하는로직수행
}
추후 새로운 국가 Country_Italy 를 추가한다면 아래와 같이 enum 만 수정하면 enum 을 사용하는 for loop 는 손대지 않아도 되는 장점이 생깁니다.
Country_Usa = 7,
Country_Italy = 8,
Country_Last = 8,
}