상세 컨텐츠

본문 제목

c++ enum보다 향상된 enum class 관련

본문

반응형

 

 

 

 

=======================

=======================

=======================

 

 

 

 

 

 

 

 

출처: http://blog.seulgi.kim/2015/11/cpp-enum-class.html

 

 C++ 03까지의 enum은 여러 가지 문제를 가지고 있었다. 그래서 그 문제들을 해결하기 위해 C++ 11은 enum class라는 것을 새로 만들었다. 이제부터 기존의 enum에 어떤 문제가 있었고, 이것을 enum class에서 어떻게 해결하였는지 살펴볼 것이다.

 우선 기존의 enum은 전방 선언할 수 없었다. 그 이유는 enumerator에 어떤 값이 들어있을지 알 수 없으면 그 크기를 정할 수 없기 때문이다. 하지만 enum class는 underlying type을 명시하지 않으면 int 타입과 같은 크기의 변수로 선언되고, int 값 안에 들어가지 못할 값을 지정하면 컴파일 에러를 발생시킨다.

  enum class Flag {
  Flag1 = 0x7FFFFFFF,
  Flag2, //enumerator value 2147483648u is outside the range of underlying type ‘int’
  Flag3 = 0xFFFFFFFF // error: enumerator value 4294967295u is outside the range of underlying type ‘int’
  };
view rawenum.cc hosted with ❤ by GitHub

 만약 int를 벗어난 범위의 값을 사용하고 싶다면, underlying type을 명시해주어야 한다.

  enum class Flag: unsigned int {
  Flag1 = 0x7FFFFFFF,
  Flag2,
  Flag3 = 0xFFFFFFFF
  };
view rawenum_class.cc hosted with ❤ by GitHub



 기존 enum의 또 다른 문제는 enumerator의 이름의 범위가 한정되지 않는다는 것이다. 예를 들어 아래와 같은 코드를 보자.

  enum IOResult {
  Error,
  Ok
  };
   
  enum ParseResult {
  Error,
  Ok
  };
view rawresult.cc hosted with ❤ by GitHub


 IO 함수의 결과와 Parse 함수의 결과를 enum으로 표현해 보았다. 하지만 이 코드는 컴파일되지 않는다. IOResult의 ErrorOk가 ParseResult의 ErrorOk와 겹치기 때문이다. 이를 해결하기 위해서는 다음과 같이 enumerator의 이름을 다르게 하거나

  enum IOResult {
  IOError,
  IOOk
  };
   
  enum ParseResult {
  ParseError,
  ParseOk
  };
view rawenum.cc hosted with ❤ by GitHub


 아래와 같이 namespace를 이용해야 했다.

  namespace IO {
  enum Result {
  Error,
  Ok
  };
  }
   
  namespace Parse {
  enum Result {
  Error,
  Ok
  };
  }


   하지만 enum class는 enumerator의 이름이 enum class 안으로 한정되기 때문에 이런 복잡한 과정이 필요 없이 그저 enum class를 선언하여 사용하면 된다.

  enum class IOResult {
  Error,
  Ok
  };
   
  enum class ParseResult {
  Error,
  Ok
  };
   
  auto io_result = IOResult::Error;
  auto parse_result = ParseResult::Ok;
view rawenum_class.cc hosted with ❤ by GitHub



 무엇보다 기존 enum의 가장 큰 문제는 정수형 변수로 암시적으로 변환되는 약 타입(weak type) 변수라는 것이다. 하지만 enum class는 정수형 변수로 암시적 변환이 되지 않는다. enum class를 정수형 변수처럼 사용하려고 하면 컴파일 에러를 발생시킨다. 만약 정수형 변수로 사용하고 싶으면 static_cast를 이용해 명시적으로 캐스팅해서 사용해야 한다.

  enum class Color {
  Red, Green, Blue
  };
   
  Color color = Color::Red;
  switch (color) {
  case 0: // error: could not convert '0' from 'int' to 'Color'
  break;
  case Color::Green:
  break;
  case Color::Blue:
  break;
  }
   
   
  if (color) { // error: could not convert 'r' from 'Color' to 'bool'
  // ...
  }
view rawcolor.cc hosted with ❤ by GitHub



 위에서 설명한 대로 기존의 enum은 전방 선언할 수 없고, enumerator 이름의 범위가 한정되지 않고, 정수형 변수로 암시적으로 변환되지도 않는다. 사실 enum의 사용법을 생각해보면 enum class가 올바른 방식이다. 하지만 C++ 11은 backward compatibility를 위해서 기존의 enum을 그대로 두고, enum class를 새로 추가하는 것을 선택했다. 이제는 compatibility를 위해서가 아니면 enum의 존재는 잊고 대신에 enum class를 사용하는 것이 좋다.

 

 

 

 

 

 

=======================

=======================

=======================

 

 

 

 

 

출처: http://egloos.zum.com/sweeper/v/2996830

 

 

1. C++03의 enum
 
 
1. Type-safety
 
 
C++03에서 enum은 type-safe하지 않았다.
 
 
enum은 암시적으로 int 이하의 정수 타입에 대입 가능했으며(암시적 형 변환), (즉 int a = Enum::AAA)
값만 동일하다면, 다른 enum 타입과도 값 비교가 가능했었다.
 
 
C++03에서 유일하게 보장한 type-safety는 아래와 같다.
  • A type의 enum을 B 타입의(즉, 다른 타입의) enum 타입으로 암시적 형 변환을 할 수 없다.
  • enum 타입의 값에 대해 정수를 직접 대입할 수 없다.
지금까지의 내용을 예제로 살펴보자.
 
 
  1. enum StateType
  2. {
  3.     ST_NONE = 0,
  4.  
  5.     ST_IDLE,
  6.     ST_MOVE,
  7.     ST_ATTACK,
  8.     ST_DEAD,
  9.  
  10.     ST_MAX,
  11. };
  12.  
  13. enum MoveType
  14. {
  15.     MT_NONE = 0,
  16.  
  17.     MT_WALK,
  18.     MT_DASH,
  19.     MT_JUMP,
  20.     MT_CROUCH,
  21.  
  22.     MT_MAX,
  23. };
  24.  
  25. int _tmain(int argc, _TCHAR* argv[])
  26. {
  27.     -- enum 값을 정수형에 대입 --
  28.     unsigned short a = ST_IDLE;
  29.  
  30.     -- enum의 크기는 int와 같다 --
  31.     int b = sizeof(StateType);
  32.  
  33.     MoveType c = MT_WALK;
  34.     -- 서로 다른 타입의 enum간 값 비교가 가능하다 --                   
  35.     if (== ST_IDLE)
  36.     {
  37.     }
  38.  
  39.     -- 다른 타입의 enum으로는 대입이 불가능하다 --
  40.     MoveType d = ST_IDLE;    
  41.  
  42.     -- 정수 값을 enum에 대입시킬 수 없다 --
  43.     StateType e = 1;
  44.  
  45.     return 0;
  46. }
 
 
2. Scope
 
 
C++03까지의 enum scope는 enum {};의 scope에 국한되지 않았다.
 
 
따라서, 위 예제처럼 ST_IDLE을 StateType의 영역지정 없이 사용이 가능했던 것이고,
당연하게도 두 개의 enum이 같은 열거 이름을 가질 수 없었다.
 
 
다음 상황의 예를 살펴보자.
  • Main.cpp에 두 개의 헤더 A.h와 B.h를 포함하고 있다.
  • A.h에 CarType이라는 enum이 있고, CarType의 열거자 중 CT_NORMAL이 포함되어 있다.
  • B.h에 CurveType이라는 enum이 있고, 역시 열거자 중 CT_NORMAL이 포함되어 있다.
이 경우 컴파일을 하게 되면, 아래와 같은 컴파일 에러가 발생한다.
 
 
error C2365: 'CT_NORMAL' : 재정의: 이전 정의는 '열거자'입니다.
 
 
 
 
 
2. C++11의 enum
 
 
C++11은 당연하게도 이전의 C++이 가지고 있던 불안정한 모습을 모두 개선시켰다.
  • enum에 대해 자유롭게 타입을 부여할 수 있다.
  • type에 대해 strong-safety를 제공할 수 있다.
  • enum 열거자의 scope 역시 enum 정의의 scope 안으로 한정지을 수 있다.
이를 위해 전통의 enum 키워드 대신 enum class를 사용할 수 있다.
 
 
C++03의 enum과 C++11의 enum class를 비교하기 위해, 앞서 사용했던 예제를 enum class를 이용하여 다시 작성해 보았다.
 
 
  1. enum class StateType : unsigned short
  2. {
  3.     ST_NONE = 0,
  4.  
  5.     ST_IDLE,
  6.     ST_MOVE,
  7.     ST_ATTACK,
  8.     ST_DEAD,   
  9.  
  10.     ST_MAX,
  11. };
  12.  
  13. enum class MoveType
  14. {
  15.     MT_NONE = 0,
  16.  
  17.     MT_WALK,
  18.     MT_DASH,
  19.     MT_JUMP,
  20.     MT_CROUCH,
  21.  
  22.     -- 열거자의 scope가 enum class {};안으로 국한되므로,
  23.         StateType의 열거 이름과 같은 것을 사용해도 무방하다. --
  24.     ST_DEAD,
  25.  
  26.     MT_MAX,
  27. };
  28.  
  29. int _tmain(int argc, _TCHAR* argv[])
  30. {
  31.     -- StateType의 열거이름은 StateType의 scope 안에서만 유효하다 --
  32.     -- 따라서, 반드시 StateType에 대한 영역 지정을 해야 한다 --
  33.     -- error C2065: 'ST_IDLE' : 선언되지 않은 식별자입니다. --
  34.     StateType a = ST_IDLE;
  35.  
  36.     -- enum class 열거자를 정수형에 암시적으로 대입할 수 없다 --
  37.     -- error C2440: '초기화 중' : 'StateType'에서 'unsigned char'(으)로 변환할 수 없습니다. --
  38.     unsigned char b = StateType::ST_IDLE;
  39.  
  40.     -- StateType은 unsigned short로 타입 지정되었으므로, 그 크기는 2 바이트이다 --
  41.     int c = sizeof(StateType);
  42.  
  43.     MoveType d= MoveType::MT_WALK;
  44.  
  45.     -- 서로 다른 타입의 열거자끼리는 비교가 불가능하다 --         
  46.     -- error C2676: 이항 '==' : 'MoveType'이(가) 이 연산자를 정의하지 않거나
  47.                                 미리 정의된 연산자에 허용되는 형식으로의 변환을 정의하지 않습니다. --
  48.     if (== StateType::ST_IDLE)
  49.     {
  50.     }
  51.  
  52.     -- 다른 타입의 enum으로는 대입이 불가능하다 --
  53.     -- error C2440: '초기화 중' : 'StateType'에서 'MoveType'(으)로 변환할 수 없습니다. --
  54.     MoveType e = StateType::ST_IDLE;    
  55.  
  56.     -- 정수 값을 enum에 대입시킬 수 없다 --
  57.     -- error C2440: '초기화 중' : 'int'에서 'StateType'(으)로 변환할 수 없습니다. --
  58.     StateType f = 1;
  59.  
  60.     return 0;
  61. }
 
 
위 내용 중 "enum에 대해 자유롭게 타입을 부여할 수 있다"는 VS2010에서도 지원이 되긴 한다.
 
 
 
 
 
 
 
 
 
즉, 아래와 같이 작성을 할 수 있는 것인데...
 
 
  1. enum StateType : unsigned short
  2. {
  3.     ST_NONE = 0,
  4.  
  5.     ST_IDLE,
  6.     ST_MOVE,
  7.     ST_ATTACK,
  8.     ST_DEAD,
  9.  
  10.     ST_MAX,
  11. };
 
 
비표준 확장이라 경고 레벨을 4로 올리면, 다음과 같은 컴파일시 경고 메시지가 출력된다.
 
 
warning C4480: 비표준 확장이 사용되었습니다. 'StateType' 열거형에 대해 내부 형식을 지정합니다.
 

 

 

=======================

=======================

=======================

 

 

 

 

 

반응형

 

728x90

 

 

 

 

출처:  http://unikys.tistory.com/376

 

* 최근에 C++로 코딩하면서 옛날에 머물러있었던 C++ 개발 실력이었기 때문에 새로 보는 내용들이 아주 많다. 지금은 그냥 여기서 그렇게 하고 있으니까 따라한다는 마음이 크지만, 조금씩은 왜 그렇게 하고 있는지 알아가면서 C++ 관련 글도 정리하면서 쓰려고 한다. 기존에 보지 못했던 키워드 중 가장 먼저 눈에 들어온 것은 enum class 였다.

 

* enum class

 

C++11에서는 기존의 enum과 다른 새로운 키워드 조합인 enum class를 선호이고 있다. 사용법은 기존의 enum에서 크게 벗어나지는 않으나 가장 다른 점은 개발자들이 기존의 enum이 가지고 있었다고 주장하는 문제점들을 해결하고자 하였다. 그 중 가장 대표적인 것은 바로 기존의 C++의 enum은 내부적으로 int와 동일하게 사용되고, 바로 int형 변수와 비교도 하고 서로 다른 enum 끼리도 비교가 가능했다. 하지만 개발자들은 이러한 경우는 맞지 않다고 생각하고 더 제약적인 enum을 만들어낸 것이 바로 enum class이다. 따라서 가장 큰 차이점은 바로 이제 enum class는 형 변환이 기존보다 제약적이 되어 개발자의 실수로 발생할 버그를 방지하고자 하는 점과 기존의 enum이 가지고 있던 스코프 문제를 해결하고자 한 것이다.

 

* 기존의 스코프 문제 해결: 스코프를 생성하는 enum class

이렇게 형의 제약이 크다고 하는 것은 기존의 enum은 정의하면 해당 스코프에 각 enum 값들이 정의되었지만, 이제는 각 enum형의 새로운 스코프 하위에 위치하게 되었다. 아래가 간단한 예이다.

// 컴파일 오류 발생! enum Color {     RED,     GREEN,     BLUE }; enum TrafficLight {     GREEN, // 오류     YELLOW,     RED  // 오류 }; 

위의 예를 보면 각 enum형 Color와 TrafficLight는 GREEN와 RED가 동일하게 정의 되어있기 때문에 컴파일 오류가 일어난다. 기존의 enum은 현재의 스코프에 해당 값들을 정의하기 때문에 문제가 발생하는 것이다. 이러한 enum들을 각각 enum class로 바꾸게 되면 문제는 해결된다.

// 컴파일 오류 해결 enum class Color { RED, GREEN, BLUE }; enum class TrafficLight { GREEN, YELLOW, RED };

단순히 이렇게만 한다고 해서 해결이 된 것이 아니라, enum class는 새로운 scope 안에 각각의 값들을 넣는 것이다. 따라서, 기존의 enum은 현재 스코프에 상수/변수가 정의된 것처럼 enum의 값들을 사용했지만, enum class는 반드시 스코프를 명시해줘야 한다. 아래가 그 차이를 나타낸다.

enum Color {     RED,     GREEN,     BLUE }; enum class TrafficLight {     GREEN,     YELLOW,     RED }; Color background = RED; // 정상 Color foreground = Color::BLUE; //C++98 오류, C++11 정상 Color fontColor = TrafficLight::RED; // 오류  TrafficLight stop = RED; // 오류 TrafficLight go = TrafficLight:GREEN; // 정상 

위와 같이 정상 케이스와 오류 케이스를 비교해보면 일반 enum은 현재의 스코프에 값이 정의되고, 별도로 스코프를 명시해도 정상동작한다. 하지만 enum class는 스코프를 명시하지 않으면 오류가 일어나므로 반드시 스코프를 명시한 다음 값을 대입해야 한다.

 

* Type safe enum class

또 다른 차이점은 바로 int 등의 값으로 변환하는 부분을 보면 알 수 있다. 기존의 enum은 int로 상호 변환이 내부적으로 자동 변환이 되었다면, enum class는 내부적으로 자동 변환하지 않고 오류를 발생시킨다. 이러한 부분은 원래 enum의 용도가 int를 사용하기 위함이 아니라 목록의 항목들을 표기하기 위함이기 때문에 조금 더 제약적인 문법을 추가하게 된 것이다. 위의 예에 이어서 보면 아래와 같다.

int rgb = RED; // 정상, 자동 변환 허용 int traffic = TrafficLight::RED; // 오류, 자동 변환 허용 안 함 int yield = static_cast<int>(TrafficLight::Yellow); //정상

어떻게 보면 int로 자동으로 변환되는 것이 좋을수도 있지만, 다른 프로그래밍 언어들의 enum이 동작하는 방식과 비교해서 더 이해하기 쉽고 공통적으로 수행하도록 하기 위해서 enum class는 명시적으로 형변환을 해줘야 한다. 이렇게 C언어가 새로운 표준으로 인하여 프로그래밍언어 자체에 대한 난이도를 낮추고 다른 언어들과 맞추어 가려고 하는 것이 돋보인다.

 

위와 같이 int로 자동으로 변경이 안된다면 다른 것보다도 불편한 점(?)이라면 바로 옵션 등과 같은 flag로서 enum을 사용하고자 한다면 명시적인 변환을 해야 하는 불편함이 있지만, 기존에 int로 사용하고자 하는 경우가 아닐 때 발생할 수 있는 오류를 방지하고자 컴파일 오류를 발생 시키는 것이다. 

 

* 선언 후 정의 기능 허용

기존의 enum과 또 다른 점이라면 특정 함수에 등에서 enum class를 사용하기 위해서는 해당 함수 위에 모든 값을 정의할 필요없이 선언 후 아래에서 별도로 정의하면 된다.

enum class Color; //선언 class FontStyle {     Color getColor(); };   enum class Color { //정의     RED,     GREEN,     BLUE }; 

이렇게 선언이 별도로 가능하게 됨으로써 enum class를 정의할 때 헤더와 CPP 파일로 분리하는 등의 관리가 가능하다.

 

* 크기 정의 및 기본 크기 할당

기존의 enum은 struct의 멤버로 설정하게 되면 컴파일하는 환경마다 struct의 실제크기는 달라질 수 있다. 그렇기 때문에 enum class에서는 기본적으로 int의 크기를 가지도록 정의하고 이를 명시적으로 정의할 수 있다. 이는 enum 키워드에도 같이 적용되어 실제 크기를 정의할 수 있게 되었다.

enum class Color : char {     RED,     GREEN,     BLUE } 

위와 같이 정의하면 각 enum의 크기는 char 크기가 되어 struct 등의 멤버로 활용되도 개발자가 임의로 설정하여 크기를 정의할 수 있다.

 

정리

enum class는 필수적으로 사용해야 되는 것은 아니지만 C++이 내재적으로 가지고 있었던 개발자의 실수로 인하여 발생하는 버그를 방지하기 위하여 나왔으며 사용할 수 있을 때에는 사용하는 것이 좋다.

 

 

 

추가로 읽을 거리

http://www.cprogramming.com/c++11/c++11-nullptr-strongly-typed-enum-class.html

http://blog.smartbear.com/c-plus-plus/closer-to-perfection-get-to-know-c11-scoped-and-based-enum-types/

http://stackoverflow.com/questions/6936030/do-we-really-need-enum-class-in-c11

http://stackoverflow.com/questions/18335861/why-is-enum-class-preferred-over-plain-enum

http://www.ibm.com/developerworks/rational/library/scoped-enums/index.html

http://stackoverflow.com/questions/12581064/enum-vs-strongly-typed-enum

http://en.cppreference.com/w/cpp/language/enum



출처: http://unikys.tistory.com/376 [All-round programmer]

 

 

 

 

=======================

=======================

=======================

 

 

 

 

출처: http://honestgame.tistory.com/46

 

 

다음은 C++11 에서 강화된 enum 기능이다.

 

1. 중복 정의 가능

 

enum  Car

{

SONATA,

AVANTE,

};

 

enum  FakeCar

{

SONATA,

AVANTE,

};

 

재정의로 에러가 난다.

 

하지만, class 한정자를 붙여주면?

 

enum  class Car

{

SONATA,

AVANTE,

};

 

enum  class FakeCar

{

SONATA,

AVANTE,

};

 

깔끔하게 컴파일 된다.

 

 

 

2. 타입 강화

 

enum  Car : unsigned int

{

SONATA = 1,

AVANTE = 2,

};

 

enum  FakeCar : unsigned int

{

SONATA2= 1,

AVANTE2 = 2,

};

 

if( Car::AVANTE == FakeCar::SONATA2 )

{

// 기존과 마찬가지로 컴파일 가능

}

 

하지만, 위와 마찬가지로 class를 붙여주면

 

enum  class Car : unsigned int

{

SONATA = 1,

AVANTE = 2,

};

 

enum  class FakeCar : unsigned int

{

SONATA2= 1,

AVANTE2 = 2,

};

 

if( Car::AVANTE == FakeCar::SONATA2 )

{

// 타입이 맞지 않으므로 에러발생!

// 물론, static_cast나 C형 캐스팅으로 변환은 가능하다

}

 

 

다시 말하면 기존처럼 enum의 약한 타입으로 인해 암시적변환이

 

class 키워드로 인해 타입이 강화된다는 것을 볼 수 있다.

 

기존에는 같은 변수를 사용하려면, namespace를 통한 방법을 사용했어야 했다.

 

 

 

 

3. 타입 정의 가능

 

enum  class Car : int

{

SONATA = 2100000000,

}; // 가능

 

enum  class Car : int

{

SONATA = 4200000000,

}; // int형의 크기를 벗어나므로 에러!

 

참고로 사이즈를 정의하지 않으면, 기본형 타입중에서 자동으로 결정이 되어진다.

 

enum  class Car  : unsigned int 

{

SONATA = 4200000000,  

}; // unsigned int 이내이므로 가능

 

 

4. 전방선언 가능

 

기존과 다르게 class처럼 전방선언이 가능해졌다.

 

 

5. 결론

 

가급적 타입이 강화된 enum class를 쓰는걸 권장한다.

 

C++11 은 vs2010부터 부분적으로 지원이 되는데, 

 

enum class는 vs2012에서 가능한 것을 확인함.

 

끝.

 

 

http://www.cprogramming.com/c++11/c++11-nullptr-strongly-typed-enum-class.html

https://msdn.microsoft.com/ko-kr/library/2dzy4k6e.aspx

 

 

 

 

 

 

 

 

=======================

=======================

=======================

 

 

 

 

 

반응형


관련글 더보기

댓글 영역