열거 형 클래스가 일반 열거 형보다 선호되는 이유는 무엇입니까?


429

타입 안전 때문에 C ++에서 enum 클래스 사용을 권장하는 사람들이 몇 명 있었습니다.

그러나 이것이 실제로 무엇을 의미합니까?


57
누군가가 어떤 프로그래밍 구조가 "악"이라고 주장 할 때, 그들은 당신이 당신 자신을 생각하지 못하게하려고합니다.
피트 베커

3
@NicolBolas : 이것은 자주 묻는 질문 (FAQ )이 다른 이야기 인지에 대한 FAQ 답변을 제공하기위한 중요한 질문 입니다.
David Rodríguez-dribeas

@David, 이것이 FAQ인지 아닌지에 대한 토론이 있습니다 . 환영합니다.
sbi

17
@PeteBecker 때때로 그들은 단지 당신을 자신으로부터 보호 하려고합니다 .
piccy

geeksforgeeks.org/… 이것은 또한 enumvs. 를 이해하기에 좋은 곳 enum class입니다.
mr_azad

답변:


472

C ++에는 두 가지 종류가 있습니다 enum.

  1. enum classes
  2. 일반 enum

다음은이를 선언하는 몇 가지 예입니다.

 enum class Color { red, green, blue }; // enum class
 enum Animal { dog, cat, bird, human }; // plain enum 

둘의 차이점은 무엇입니까?

  • enum classES - 열거 이름은 지역 열거 및 그 값은 할 수 없습니다 암시 적으로 (다른 같은 다른 형태로 변환 enum또는 int)

  • Plain enums-열거 자 이름이 열거와 동일한 범위에 있고 그 값이 암시 적으로 정수 및 다른 유형으로 변환되는 위치

예:

enum Color { red, green, blue };                    // plain enum 
enum Card { red_card, green_card, yellow_card };    // another plain enum 
enum class Animal { dog, deer, cat, bird, human };  // enum class
enum class Mammal { kangaroo, deer, human };        // another enum class

void fun() {

    // examples of bad use of plain enums:
    Color color = Color::red;
    Card card = Card::green_card;

    int num = color;    // no problem

    if (color == Card::red_card) // no problem (bad)
        cout << "bad" << endl;

    if (card == Color::green)   // no problem (bad)
        cout << "bad" << endl;

    // examples of good use of enum classes (safe)
    Animal a = Animal::deer;
    Mammal m = Mammal::deer;

    int num2 = a;   // error
    if (m == a)         // error (good)
        cout << "bad" << endl;

    if (a == Mammal::deer) // error (good)
        cout << "bad" << endl;

}

결론:

enum class버그로 이어질 수있는 놀라움이 줄어들 기 때문에 es를 선호해야합니다.


7
좋은 예 ... 클래스 버전의 형식 안전성과 열거 형 버전의 네임 스페이스 승격을 결합하는 방법이 있습니까? 즉, A상태 가있는 클래스가 있고 클래스 enum class State { online, offline };의 자식으로을 만드는 경우 내부 대신 검사 A를 수행하고 싶습니다 ... 가능합니까? state == onlineAstate == State::online
마크

31
아니. 네임 스페이스 프로모션은 Bad Thing ™이며 그에 대한 반감은 enum class이를 제거하는 것이 었습니다.
강아지

10
C ++ 11에서는 enum Animal : unsigned int {dog, deer, cat, bird}와 같이 명시 적으로 형식화 된 열거 형을 사용할 수도 있습니다.
Blasius Secundus

3
@Cat Plus Plus @Oleksiy가 나쁘다고 말합니다. 내 질문은 Oleksiy가 나쁘다고 생각한 것이 아닙니다. 내 질문은 세부 사항에 요청했다 무엇 그것에 대해 나쁘다. 특히, 올렉시는, 예를 들어,에 대한 나쁜 생각 않습니다 Color color = Color::red.
chux-복원 Monica Monica

9
@ Cat Plus Plus 따라서 예제의 나쁜 점if (color == Card::red_card)주석보다 4 줄 늦게 나타날 때까지 발생하지 않습니다 (이제 블록의 전반에 적용됩니다). 블록의 2 줄은 나쁜 예를 제공합니다 . 처음 3 줄은 문제가되지 않습니다. "전체 블록은 일반 열거 형이 나쁜 이유입니다."라고 생각했습니다. 나는 지금 단지 셋업 일 뿐이다. 어쨌든 피드백에 감사드립니다.
chux-복원 Monica Monica

248

에서 비얀 스트로브 스트 룹의 C ++ 11 FAQ :

enum classES ( "새 열거 형", "강한 열거 형") 주소를 기존의 C ++ 열거 세 가지 문제를 :

  • 일반 열거 형은 암시 적으로 int로 변환되어 열거 형이 정수로 작동하지 않으려는 경우 오류가 발생합니다.
  • 기존 열거 형은 열거자를 주변 범위로 내보내 이름 충돌을 일으 킵니다.
  • 의 기본 유형을 enum지정할 수 없으므로 혼동, 호환성 문제가 발생하고 앞으로 선언 할 수 없습니다.

새 열거 형은 기존 열거 형 (이름 값)의 측면과 클래스의 측면 (범위 지정된 멤버 및 변환 부재)을 결합하기 때문에 "enum class"입니다.

따라서 다른 사용자가 언급했듯이 "강력한 열거 형"은 코드를 더 안전하게 만듭니다.

"클래식"의 기본 유형은 enum모든 값에 맞도록 충분히 큰 정수 유형이어야합니다 enum. 이것은 일반적으로int 입니다. 또한 열거 된 각 유형 char은 부호있는 / 부호없는 정수 유형 과 호환 가능해야합니다 .

이것은 enum기본 유형 이 무엇인지에 대한 광범위한 설명 이므로 각 컴파일러는 기본 유형의 기본 유형에 대해 스스로 결정을 내립니다.enum 수 있으며 때로는 결과가 놀랍습니다.

예를 들어, 나는 이와 같은 코드를 여러 번 보았다.

enum E_MY_FAVOURITE_FRUITS
{
    E_APPLE      = 0x01,
    E_WATERMELON = 0x02,
    E_COCONUT    = 0x04,
    E_STRAWBERRY = 0x08,
    E_CHERRY     = 0x10,
    E_PINEAPPLE  = 0x20,
    E_BANANA     = 0x40,
    E_MANGO      = 0x80,
    E_MY_FAVOURITE_FRUITS_FORCE8 = 0xFF // 'Force' 8bits, how can you tell?
};

위의 코드에서 일부 순진 코더는 컴파일러가 저장 것이라고 생각 E_MY_FAVOURITE_FRUITS부호없는 8 비트 타입으로 값을 ...하지만 그것에 대해 어떠한 보증도 없다 : 컴파일러는 선택할 수있다 unsigned char거나 int또는 short그 유형의 모든에 맞게 충분히 큰이며, 에 표시된 값 enum. 필드를 추가하는 E_MY_FAVOURITE_FRUITS_FORCE8것은 부담이며 컴파일러가의 기본 유형에 대해 어떤 종류의 선택도하지 않습니다 enum.

유형 크기에 의존하는 코드가 있거나 E_MY_FAVOURITE_FRUITS너비가 있다고 가정하면 (예 : 직렬화 루틴)이 코드는 컴파일러의 생각에 따라 이상한 방식으로 작동 할 수 있습니다.

문제를 악화시키기 위해 일부 직장 동료가 부주의하게 우리의 새로운 가치를 부여한다면 enum:

    E_DEVIL_FRUIT  = 0x100, // New fruit, with value greater than 8bits

컴파일러는 그것에 대해 불평하지 않습니다! enum(컴파일러가 가능한 가장 작은 유형을 사용한다고 가정 할 때, 우리가 할 수없는 가정)의 모든 값에 맞게 유형의 크기를 조정합니다 . 이 간단하고 부주의 한 추가는 enum관련 코드를 깨뜨릴 수있는 미묘한 부분입니다.

C ++ 11은 enum및에 대한 기본 유형을 지정할 수 있으므로 enum class( rdb 덕분에 )이 문제는 깔끔하게 해결됩니다.

enum class E_MY_FAVOURITE_FRUITS : unsigned char
{
    E_APPLE        = 0x01,
    E_WATERMELON   = 0x02,
    E_COCONUT      = 0x04,
    E_STRAWBERRY   = 0x08,
    E_CHERRY       = 0x10,
    E_PINEAPPLE    = 0x20,
    E_BANANA       = 0x40,
    E_MANGO        = 0x80,
    E_DEVIL_FRUIT  = 0x100, // Warning!: constant value truncated
};

필드에이 유형의 범위를 벗어난 표현식이있는 경우 기본 유형을 지정하면 기본 유형을 변경하는 대신 컴파일러가 불평합니다.

이것이 좋은 안전 개선이라고 생각합니다.

그렇다면 왜 열거 형 클래스가 일반 열거 형보다 선호됩니까? scoped ( enum class) 및 unscoped ( enum)에 대한 기본 유형을 선택할 수 있다면 enum class더 나은 선택 은 무엇 입니까? :

  • 암시 적으로 변환하지 않습니다. int .
  • 주변 네임 스페이스를 오염시키지 않습니다.
  • 그것들은 앞으로 선언 될 수 있습니다.

1
나는 우리가 우리가 C ++ 11 가지고뿐만 아니라 일반 열거 형의 열거 기본 유형을 제한 할 수 있습니다 가정
사가르 Padhye

11
죄송하지만이 답변은 잘못되었습니다. "enum class"는 유형을 지정하는 기능과 관련이 없습니다. 그것은 정규 열거 형과 열거 형 클래스 모두에 존재하는 독립적 인 기능입니다.
rdb

14
이것은 거래입니다. * Enum 클래스는 C ++ 11의 새로운 기능입니다. * 형식화 된 열거 형은 C ++ 11의 새로운 기능입니다. 이들은 C ++ 11의 두 가지 별도의 관련이없는 새로운 기능입니다. 둘 다 사용하거나 둘 중 하나를 사용하거나 둘 다 사용할 수 없습니다.
rdb

2
Alex Allain 은이 블로그 ([ programming.com/c++11/…] 에서 아직 보지 못한 가장 완벽한 간단한 설명을 제공한다고 생각합니다 . 전통적인 enum 은 정수 값 대신 이름을 사용하고 선행 처리기 #defines를 사용하지 않는 것이 좋았습니다. enum 클래스열거 자의 숫자 값 개념을 제거하고 범위와 강력한 타이핑을 도입하여 프로그램의 정확성을 높일 수 있습니다 . 생각 객체 지향에 한 걸음 더 가까이 다가갑니다.
Jon Spencer

2
따로, 코드를 검토 할 때 항상 재미 있고 갑자기 One Piece 가 발생합니다.
저스틴 타임-복원 모니카

47

일반 열거 형에 비해 열거 형 클래스를 사용하는 기본 이점은 두 개의 다른 열거 형에 대해 동일한 열거 형 변수가있을 수 있으며 여전히 해결할 수 있다는 것입니다 ( 유형 안전 으로 언급되었습니다) OP에 의해 )

예를 들어 :

enum class Color1 { red, green, blue };    //this will compile
enum class Color2 { red, green, blue };

enum Color1 { red, green, blue };    //this will not compile 
enum Color2 { red, green, blue };

기본 열거 형의 경우 컴파일러는 red유형을 참조 하는지 Color1또는 Color2아래 명령문과 같이 구분할 수 없습니다 .

enum Color1 { red, green, blue };   
enum Color2 { red, green, blue };
int x = red;    //Compile time error(which red are you refering to??)

1
@Oleksiy Ohh 귀하의 질문을 제대로 읽지 못했습니다. 모르는 사람들을위한 부가 기능으로 고려하십시오.
Saksham

괜찮아! 나는 이것에 대해 거의 잊었다
Oleksiy

물론 enum { COLOR1_RED, COLOR1_GREE, COLOR1_BLUE }네임 스페이스 문제를 쉽게 피할 수 있습니다. 네임 스페이스 인수는 여기서 언급하지 않은 세 가지 중 하나입니다.
Jo So

2
@Jo So 그 해결책은 불필요한 해결책입니다. Enum : enum Color1 { COLOR1_RED, COLOR1_GREEN, COLOR1_BLUE }은 Enum 클래스와 비슷합니다 enum class Color1 { RED, GREEN, BLUE }. 액세스는 비슷합니다 : COLOR1_REDvs. Color1::RED그러나 Enum 버전에서는 각 값에 "COLOR1"을 입력해야하므로 enum 클래스의 네임 스페이스 동작에서 피할 수있는 오타 공간이 더 많습니다.
cdgraham

2
건설적인 비판을 사용하십시오 . 더 많은 오타를 말할 때, 나는 원래 값을 정의 할 때 enum Color1컴파일러가 여전히 '유효한'이름이기 때문에 잡을 수없는 것을 의미 합니다. 열거 형 클래스를 사용하여을 쓰는 RED등의 경우 값 (네임 스페이스 인수)에 액세스하기 위해 지정해야 GREEN하기 enum Banana때문에 해결할 수 없습니다 Color1::RED. 사용 enum하기에는 여전히 좋은 시간이 있지만의 네임 스페이스 동작은 enum class종종 매우 유용 할 수 있습니다.
cdgraham

20

열거는 정수 값 집합을 나타내는 데 사용됩니다.

class뒤에 나오는 키워드 enum는 열거가 강력하게 입력되고 열거 자의 범위가 지정됨을 지정합니다. 이런 식으로 enum클래스는 실수로 상수를 잘못 사용하는 것을 방지합니다.

예를 들어 :

enum class Animal{Dog, Cat, Tiger};
enum class Pets{Dog, Parrot};

여기서는 Animal과 Pets 값을 혼합 할 수 없습니다.

Animal a = Dog;       // Error: which DOG?    
Animal a = Pets::Dog  // Pets::Dog is not an Animal

7

C ++ 11 FAQ 는 다음 사항을 언급합니다.

일반 열거 형은 암시 적으로 int로 변환되어 열거 형이 정수로 작동하지 않으려는 경우 오류가 발생합니다.

enum color
{
    Red,
    Green,
    Yellow
};

enum class NewColor
{
    Red_1,
    Green_1,
    Yellow_1
};

int main()
{
    //! Implicit conversion is possible
    int i = Red;

    //! Need enum class name followed by access specifier. Ex: NewColor::Red_1
    int j = Red_1; // error C2065: 'Red_1': undeclared identifier

    //! Implicit converison is not possible. Solution Ex: int k = (int)NewColor::Red_1;
    int k = NewColor::Red_1; // error C2440: 'initializing': cannot convert from 'NewColor' to 'int'

    return 0;
}

기존 열거 형은 열거자를 주변 범위로 내보내 이름 충돌을 일으 킵니다.

// Header.h

enum vehicle
{
    Car,
    Bus,
    Bike,
    Autorickshow
};

enum FourWheeler
{
    Car,        // error C2365: 'Car': redefinition; previous definition was 'enumerator'
    SmallBus
};

enum class Editor
{
    vim,
    eclipes,
    VisualStudio
};

enum class CppEditor
{
    eclipes,       // No error of redefinitions
    VisualStudio,  // No error of redefinitions
    QtCreator
};

열거 형의 기본 유형을 지정할 수 없으므로 혼동, 호환성 문제가 발생하고 앞으로 선언 할 수 없습니다.

// Header1.h
#include <iostream>

using namespace std;

enum class Port : unsigned char; // Forward declare

class MyClass
{
public:
    void PrintPort(enum class Port p);
};

void MyClass::PrintPort(enum class Port p)
{
    cout << (int)p << endl;
}

.

// Header.h
enum class Port : unsigned char // Declare enum type explicitly
{
    PORT_1 = 0x01,
    PORT_2 = 0x02,
    PORT_3 = 0x04
};

.

// Source.cpp
#include "Header1.h"
#include "Header.h"

using namespace std;
int main()
{
    MyClass m;
    m.PrintPort(Port::PORT_1);

    return 0;
}

C ++ 11에서는 "비 클래스"열거 형도 입력 할 수 있습니다. 네임 스페이스 오염 문제 등이 여전히 존재합니다. 오래 전에 존재했던 관련 답변을 살펴보십시오 ..
user2864740

7
  1. 암시 적으로 int로 변환하지 마십시오
  2. 어떤 유형의 기초를 선택할 수 있습니까
  3. 오염이 발생하지 않도록 ENUM 네임 스페이스
  4. 일반 클래스와 비교하여 앞으로 선언 할 수 있지만 메소드가 없습니다.

2

C ++ 20은 다음과 같은 다른 대답들 외에도 다음과 같은 문제 중 하나를 해결한다는 점에 주목할 가치가 enum class있습니다. 가상의 상상 enum class, Color.

void foo(Color c)
  switch (c) {
    case Color::Red: ...;
    case Color::Green: ...;
    case Color::Blue: ...;
    // etc
  }
}

enum이름이 전체 범위에 있으므로 접두사를 붙일 필요가없는 일반 변형 과 비교하여 자세합니다 Color::.

그러나 C ++ 20 using enum에서는 열거 형의 모든 이름을 현재 범위에 도입하여 문제를 해결할 수 있습니다.

void foo(Color c)
  using enum Color;
  switch (c) {
    case Red: ...;
    case Green: ...;
    case Blue: ...;
    // etc
  }
}

따라서 지금을 사용하지 않을 이유가 없습니다 enum class.


1

다른 답변에서 언급했듯이 클래스 열거 형은 암시 적으로 int / bool로 변환 할 수 없으므로 다음과 같은 버그가있는 코드를 피하는 데 도움이됩니다.

enum MyEnum {
  Value1,
  Value2,
};
...
if (var == Value1 || Value2) // Should be "var == Value2" no error/warning

2
이전의 주석을 완료하기 위해 gcc에는 -Wint-in-bool-context라는 경고가 표시되어 있으며 이러한 오류를 정확하게 포착합니다.
Arnaud

0

명시 적으로 언급되지 않은 한 가지-범위 기능은 열거 형과 클래스 메소드에 대해 동일한 이름을 갖는 옵션을 제공합니다. 예를 들어 :

class Test
{
public:
   // these call ProcessCommand() internally
   void TakeSnapshot();
   void RestoreSnapshot();
private:
   enum class Command // wouldn't be possible without 'class'
   {
        TakeSnapshot,
        RestoreSnapshot
   };
   void ProcessCommand(Command cmd); // signal the other thread or whatever
};
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.