#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 값과

 

enum Animal { dog, cat, fish, bird };

 

문자로이뤄진char* 값을매칭시키는방안에대한내용입니다.

 

wchar_t* AnimalDiscription[] = { L"dog", L"cat", L"fish", L"bird" };

 

이를 위해 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)

};

 

 

3.  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,

}

 

이상 enum 을 활용하는 몇 가지 팁을 알아 봤습니다.  혹시 나만의 좋은 팁이 있으시면 같이 공유해 주세요.

 

 


'개발 > C/C++' 카테고리의 다른 글

[C/C++]자주 하는 실수  (15) 2008.10.22
[C++]소멸자에서 가상함수 호출하기  (8) 2008.10.08
[C++]포인터 Wrapping 클래스 만들기  (4) 2008.09.09
[C/C++] enum, 보다 나은 enum  (19) 2008.07.21
싱글톤 클래스  (5) 2008.07.01
[C++]STL Container 조합하기  (3) 2007.11.12
[C/C++]유용한 #pragma directive  (9) 2007.09.05
  1. BlogIcon object 2008.07.22 03:13 신고

    http://minjang.egloos.com/517687

    제가 왕년에 enum의 부족한 기능때문에 참 고생 많이 했죠. 저도 namespace로 묶는 것을 기본으로 했고 거기다가 enum <-> string 연산해주는 것도 묶다보니 제법 일이 커지더군요. 이런 점에선 역시 C#이 좋습니다.

    • BlogIcon esstory 2008.07.22 07:54 신고

      꽤 오래전 글인데, enum 과 #define 을 이용해서 멋지게 확장해서 이미 쓰고 계셨네요.
      좋은 글 잘 봤습니다.
      #define 은 제가 만든건 쉬워보이는데 남이 짠건 다 어려워보여요. ㅋㅋ. 남들도 그렇기 때문에 #define 을 쓰지 말자는 의견이 많은건 아닌지 ㅎ

      사실 요즘 나오는 언어들에 비하면 C++ 에서 이러한 삽질들은 좀 허무한 면이 있지요. 그래도 C++ 이 아직은 젤 좋아요.

    • BlogIcon object 2008.07.22 14:20 신고

      2003년에 최초로 저걸 만들었습니다. 최초의 목표는 scope가 있는 enum을 만드는 것이었죠. 처음에는 namespace 대신에 class로 구현을 했었습니다. 그러다가 해보니 namespace가 훨씬 더 깔끔하더군요. 특히 인텔리센스로 들어가면 namespace로 할 경우 딱 enum 값들만 리스트에 나옵니다. 그리고 여기에 enum에 대응되는 스트링을 쉽게 찾을 수 있어야 한다.. 이거 목표를 위해 또 삽질을 했죠. 제가 만드는 프로젝트가 수 많은 enum 값이 내부에 정의가 되고 이걸 UI를 통해 또 사람이 읽을 수 있는 스트링으로 바꿔야 하는데 이게 참 쉽지 않은 문제였습니다. 제가 이 매크로 만들기 이전에는 놀랍게도 전부다 #define으로 다 만들고 이걸 모두 스트링으로 바꿔주는 함수를 다들 노가다로 만들었다는... 아 옛날 삽질한 생각하니 눈물이 앞을 가리네요.

      #define은 정말 어렵긴 어렵습니다. 저도 지금 저거 보면 잘 이해가 안갑니다.

    • BlogIcon esstory 2008.07.22 15:10 신고

      그전에 만든 코드는 모두 #define 과 노가다 코드였다니--;; 일일이 enum 매크로로 수정하는 작업도 엄청났겠습니다.
      처음에는 그닥 고민할 필요가 없었던 일들이 , 프로그램 덩치가 커지고, 종류가 늘어나면서 더 이상 기존 방법으로는 유지보수하기 힘들어 지는 시점이 오곤 하는데 object님처럼 이를 타파하기 위해 방법을 만드는 사람도 있고, 어차피 망가졌는데 더 망가짐 어떠냐 하고 그냥 대충 수정하는 사람도 있고 하네요..

      누군가 필요성을 공감하고 고민해서 해결하는 사람이 많으면 많을 수록 해당 프로젝트로 오래 살아 남는게 아닌가 싶습니다.

      #define 은 참 오묘해요 :)

  2. BlogIcon 오스카 2008.07.22 10:26 신고

    상수 값이 계속 많아지다 보니, enum(위에 말씀하신 namespace 쓰는 형태) 정의 헤더를 생성하는 스크립트를 Pre-Build에 돌려버렸던 기억이... (사실 일반 코드도 그렇게 생성하는 방식을 가끔....)

    매크로로 하는 방법도 괜찮네요. 흐음...

    • BlogIcon esstory 2008.07.22 10:44 신고

      결국은 큰 프로젝트에서, 어떡하면, 에러 없이 유지 보수 하기 좋은 코드를 가져가냐는 건데..

      오스카님도 좋은 방법 많이 공유해 주세요 :)

  3. BlogIcon TTF 2009.01.07 21:24 신고

    esstory님이 작성하신 블로그를 보고, 정리하여 제 블로그에 블로깅 하였습니다.
    좋은 정보 감사드립니다.

    http://www.npteam.net/695

    • BlogIcon esstory 2009.01.07 21:47 신고

      TTF 님 안녕하세요
      자신걸로 완벽하게 만드는게 중요한거 같습니다.
      즐거운 프로그래밍 하세요 ^^;

  4. BlogIcon blueasa 2010.03.21 20:31 신고

    좋은정보 감사합니다. :)

  5. BlogIcon 좡이 2011.10.25 13:57 신고

    1번 팁을 항상 까먹어서 또 다시 검색하고 들어오게 되내요 ..^^
    좋은 정보 감사합니다 ^^

    • BlogIcon esstory 2011.10.30 15:02 신고

      저도 제가 써 놓고 자주 보는걸요 찾아 주셔서 감사합니다 :)

  6. BlogIcon 핑크팬더 2012.02.03 14:56 신고

    좋은 팁 감사합니다. (특히 1,2번 졸린 머릿결이 확 트이네요 ^^)

    특히 3번의 경우엔 제가 일본에서 프로그래밍 할때 써먹던거라 반가운 기분이 드네요.
    참고로 3번의 팁경우 입니다만, 저는 맨 앞과 뒤를 이렇게 사용합니다.

    Country_Fisrt = 0,
    Country_China = 0,
    Country_England = 1,
    ...

    보다는

    Country_Fisrt = 0,
    Country_China = Country_Fisrt,
    Country_England,
    ...
    Country_Italy,
    Country_Last,

    이렇게 해놓으면, 열거형은 어차피 순차적으로 정의가 되고
    이탈리아 다음에 마지막 열거를 했기때문에
    for문을 쓸경우

    for(int i=Country_First; i<Country_Last; ++i)
    식으로 <= 보단 <로 좀더 간단하게 사용할 수 있어서 이쪽을 좀더 애용하고 있습니다.

  7. BlogIcon 꿀두유 2012.05.24 13:49 신고

    와. 정말 좋은 팁입니다.
    특히 저에겐 2번과 같은 방법이 필요했었는데, 프로젝트에 적용 시켜봐야겠군요.
    1번 처럼 namespace를 이용하는 방법은 가독성이 뛰어나 많이 사용하고 있습니다.

    그런데 한가지 궁금한 점은,
    저 역시도 대부분의 멤버 변수는 private으로 감싸둬,
    매번 클래스 생성시 Get/Set Property를 생성하는데 매크로로 정의하여 편히 사용한다는 구문이 눈에 가네요. 어떻게 하셨는지 알려주시면 감사하겠습니다! :)

    • BlogIcon esstory 2012.05.24 20:20 신고

      Get/Set Property를 생성 매크로는 http://eslife.tistory.com/159

      위 글 보시면 됩니다~

  8. daldan 2015.11.04 13:28 신고

    후위 증가 및 감소 연산은 열거형 형식에서 지원되지 않습니다.
    ( https://msdn.microsoft.com/ko-kr/library/e1e3921c.aspx )

    enum Compass { North, South, East, West );
    Compass myCompass;
    for( myCompass = North; myCompass != West; myCompass++ ) // Error

  9. David 2015.11.10 11:20 신고

    안녕하세요.
    enum 에 문자열 결합하는 내용 잘 읽었습니다. 몇 번이나 다시 읽었네요..

    질문 하나만 드려도 될까요?
    enum DevType { TYPE_A=100, TYPE_B=200, TYPE_C=300 } 이렇게 선언해두고,
    AddNewInfo(TYPE_A); 와 같이 사용중인 코드가 있습니다.

    파일에서 문자열("TYPE_A";)을 읽어와서 이 문자열로 enum에서 선언한 상수값을 찾아 AddNewInfo(TYPE_A); 와 같은 함수의 인자로 사용하려고 합니다.
    이런 경우에 2번 내용의 enum 에 문자열 결합하는 방법을 어떻게 응용해서 사용하면 될까요?

    • BlogIcon esstory 2015.11.10 22:24 신고

      특별히 좋은 방법은 없을거 같습니다.
      함수 하나 만들어서 결국 문자열 비교 --> enum 으로 변환하는 방법 밖에 제가 아는 한 없어 보여요
      자바처럼 enum 이 클래스로 되어 있으면 방법이 있는데 어차피 위 방법은 언어의 제한을 조금 벗어난 수준 정도라 노가다가 필요 합니다 ^^

  10. 동그리 2015.11.30 11:49 신고

    좋은글 감사합니다. 링크 퍼갈께요~*

  11. Joseph.C 2017.04.26 13:23 신고

    감사합니다. 위에 책 링크가 깨진 것 같아보여서 이게 맞는지는 모르겠지만,
    구글에서 뜨는 링크로 보내드려봅니다. http://gamesfromwithin.com/stupid-c-tricks-2-better-enums

+ Recent posts