<개인공부>/[C++]

[C++] enum class (scoped enum) 에 대해서

BlockDMask 2020. 7. 30. 15:27
반응형

안녕하세요. BlockDMask 입니다.

오늘은 C++11에서 추가된 enum class에 대해서 알아볼까 합니다.

지금이 2020년도이니, 이미 당연스러운 C++ 문법이라고 생각하실 수도 있겠네요.

enum이랑 enum class가 무엇이 다른지, 굳이 C++11에 이런걸 추가한 이유는 무엇인지

enum class를 추가 함에 따라 얻는 이점이 무엇인지, 언제 써야하는지 등을 중점으로 해서 글을 읽어주시면 좋을것 같습니다.

그럼 시작해보겠습니다.


<목차>

  1. enum class가 나오게 된 배경
  1. enum class 사용 방법
  1. enum class를 사용함으로써 얻는 이점 3가지
  1. enum class 정리

1. C++11에서 enum class가 나오게 된 배경에 대해서.


enum class가 나오게 됨 배경은 당연하게도 기존의 enum의 부족함 혹은 단점을 보안하기 위해 나온것 입니다.

기존 C++98에서 enum의 문제점이라고 한다면 enum은 별도의 이름공간(namespace)를 가지고 있지 않는 것이라 할 수 있습니다. enum을 여러개 사용하다 보면 이름이 중복되는게 분명히 나오게 됩니다.

물론, 접두 접미에 enum 타입의 이름을 붙인 다거나 해서 이름을 구분 할 수는 있겠지만 그것은 어디까지나 프로그래머가 임의로 변수 이름을 잘 구분하기 위해서 지정한것 이었습니다.

그걸 이제 언어 단에서 구분을 해주기 위해서 enum class 라는 것을 도입했습니다.

간단히 이야기하면 enum 들을 정확하게 구분하기 위해서 enum class가 도입된 것으로 추측할 수 있습니다. (정확한 이유는 모르겠지만)

그럼 기존 enum의 부족한 부분을 코드로 한번 보겠습니다.

#include<iostream>
using namespace std;

enum ECar{
    BMW,
    AUDI,
    KIA,
    BENZ,
    HYUNDAE,
    PORSCHE,
};

enum EKoreaCar{
    KIA,
    HYUNDAE,
};

int main(void)
{
    int num = KIA; //error
    cout << "KIA : " << num << endl;
    return 0;
}

코드를 짜다 보면 KIA, HYUNDAE 처럼 열거자 이름들이 중복 되는 경우들이 있습니다.

이런경우에는 컴파일러가 이 KIA가 ECar에 속한 KIA인지 EKoreaCar에 속한 KIA인지 알 수가 없습니다.

요즘에는 IDE가 좋아져서 redefine 되었다고 경고 문구를 출력해주기도 합니다.

아무튼, 결론적으로 기존의 enum은 저런식으로 enum의 열거자 이름들을 중복해서 사용할 수 없습니다.

기존에는 enum을 겹치게 하지 않기 위해 저는 이런식으로 우회 해서 개발하긴 했습니다.


1. enum의 이름이 중복되지 않게 접두, 접미를 넣어서 사용

#include<iostream>
using namespace std;
enum ECar{
    cBMW,
    cAUDI,
    cKIA,
    cBENZ,
    cHYUNDAE,
    cPORSCHE,
};

enum EKoreaCar{
    kcKIA,
    kcHYUNDAE,
};

int main(void)
{
    int num = cKIA;
    cout << "KIA : " << num << endl;
    return 0;
}



2. 이름이 중복되는 enum이 생길경우 별도의 namespace를 만들어서 사용

#include<iostream>
using namespace std;

namespace TYPEA{
    enum ECar{
        BMW,
        AUDI,
        KIA,
        BENZ,
        HYUNDAE,
        PORSCHE,
    };
}

namespace TYPEB{
    enum EKoreaCar{
        KIA,
        HYUNDAE,
    };
}


int main(void)
{
    int num = TYPEA::KIA;
    cout << "KIA : " << num << endl;
    return 0;
}

이렇게 두가지 방법으로 우회해서 enum을 구분해서 그동안 사용했었습니다.

하지만 enum class가 나온 후 부터는 더이상 이런 작업이 불필요해졌습니다.

아래 enum class 코드를 우선 미리 보고 넘어가볼까요?



3. C++11에 enum class를 도입함으로 아래와 같은 작업이 가능해졌습니다.

#include<iostream>
using namespace std;

enum class ECar{
    BMW,
    AUDI,
    KIA,
    BENZ,
    HYUNDAE,
    PORSCHE,
};

enum class EKoreaCar{
    KIA,
    HYUNDAE,
};


int main(void)
{
    int num = static_cast<int>(ECar::KIA);
    cout << "KIA : " << num << endl;
    return 0;
}

어떤가요? class 키워드를 하나 추가했을 뿐인데 훨씬 보기 편하고 그런가요?




2. enum 클래스 사용법, 모양


위의 예제에서 보셨겠지만 enum class의 모양은 enum뒤에 class만 붙여주면 enum class가 됩니다.

참 간단하죠?

enum class [열거형이름]
{
	//..열거자..
}

호출할때는

열거형이름::열거자

이런식으로 호출을 하면됩니다. 한번 코드로 볼까요?

enum class EColor
{
	RED,
	BLUE,
	GREEN,
	YELLOW,
}

enum class ERainbowColor
{
	RED,
	ORANGE,
}

이런식으로 enum이 선언, 정의 되어있다고 할때

EColor 열거형의 RED, ERainbowColor 열거형의 RED를 부를때 각각 열거형(enum)의 이름을 앞에 두고 부르면 됩니다. 간단하죠?

EColor::RED
ERainbowColor::RED

하지만, enum에서 편하게? 깊게 생각하지 않고 당연하게 생각했던것이 enum class를 사용하게 되면 조금 바뀌게 됩니다. 주제 3번으로 넘어가볼까요?



3. C++ enum class를 사용할때의 이점, 주의점 3가지


1) 열거자 이름에 의한 중복이 없다.

위에서 언급했듯이 enum class는 enum과 달리 enum에 이름공간을 지어줍니다.

그렇기 때문에 코드를 작성할때 좀더 편리하고 정확하게 enum을 가지고 놀 수 있게 되었습니다.


2) 타입 변환에 좀 더 까다로움.

기존의 enum 은 암시적으로 int 값으로 형변환이 되어서 아래와 같은 코드가 가능했습니다.

#include<iostream>
using namespace std;
enum ESubject{
    MATH,
    KOREAN,
    ENGLISH,
};

int main(void)
{
    if(KOREAN == 1) {
        cout << "KOREAN is 1" << endl;
    }
    else {
        cout << "KOREAN is not 1" << endl;
    }
    
    return 0;
}

어떻게 보면 enum값이 암시적 형변환이 되어서 int 와 비교가 가능하다는 것은 이점으로 보일 수 있지만, 프로그래머가 예상하지 못한 휴먼 에러를 발생할 수 있습니다.

enum class를 도입함으로써 더이상 int 로 암시적 형변환이 되는게 불가능해졌습니다. 이는 기존의 enum 암시적 형변환을 유용하게 사용하셨던 분들께는 안좋은 소식일 수 있지만. 이렇게 까다롭게 열거자들의 형식이 강력하게 제한된다는 점이 우리 개발자들이 만들 수 있는 휴먼에러의 위험성을 줄일 수 있다는 이점이 존재합니다.

이제 명확하게 명시적 형변환을 통해서 열거자를 비교해야 합니다.

#include<iostream>
using namespace std;
enum class ESubject{
    MATH,
    KOREAN,
    ENGLISH,
};

int main(void)
{
		// static_cast 를 이용해서 int 타입으로 형변환.
		// 이름공간(ESubject::)을 통해서 KOREAN 에 접근합니다. 

    if(static_cast<int>(ESubject::KOREAN) == 1) {
        cout << "KOREAN is 1" << endl;
    }
    else {
        cout << "KOREAN is not 1" << endl;
    }
    
    return 0;
}


3) 전방선언 가능.

전방선언(forward declaration) 이라는게 무엇인가 하면 선언을 먼저하고 정의를 나중에 해도 된다는 것 입니다.

#include<iostream>
using namespace std;
enum class ESubject : int; //전방선언

int main(void)
{
		//선언한걸 사용
    if(static_cast<int>(ESubject::KOREAN) == 1) {
        cout << "KOREAN is 1" << endl;
    }
    else {
        cout << "KOREAN is not 1" << endl;
    }
    
    return 0;
}

//enum class정의
enum class ESubject : int
{
    MATH,
    KOREAN,
    ENGLISH,
};

위 코드에서 보면 선언할때 뒤에 데이터타입을 지정해 주었죠? enum class 전방 선언을 하기 위해서는 꼭 이 작업이 필요합니다.

뒤에 데이터 타입을 지정해 주는것을 "바탕형식을 지정"해준다 혹은 "타입선언"을 해준다고 합니다.

왜냐하면, 컴파일러가 미리 선언을 통해서 메모리를 잡고 있어야 하기 때문에 바탕형식(어떤 타입으로 enum을 사용할건지) 지정을 해야합니다.

*추가적으로 바탕형식(=타입선언)은 기존의 enum에도 선언이 가능합니다. 하지만 기존의 enum에서는 전방선언이 불가능합니다.


3. enum class 요약 및 아는거 전달


enum class를 쓰면 "열거형이름::열거자" 이런식으로 접근이 가능하다.

enum class를 쓰면 중복되는 열거자 이름이 있어도 괜찮다.

enum class를 쓰면 암시적 형변환이 막히기 때문에 명시적 형변환을 해야한다.

enum class를 쓰면 전방 선언이 가능하다.

enum은 범위없는(unscoped) enum 이라 부르고, enum class 를 범위있는(scoped) enum이라 부릅니다.

마지막으로 전달하고 싶은 메시지는 "enum class가 있다고해서 무조건적으로 enum class를 사용해라!"가 아니라,

enum class 를 사용할때 이점이 이런것이 있으며, 기존의 C++98의 enum만으로도 충분한 경우도 있습니다.

그러니, 개발 혹은 설계 하실때 구분을 하면서 구현을 하면 더 좋은 코드가 나오지 않을까 싶습니다.

감사합니다. BlockDMask 이었습니다.



반응형