-
[Effective Modern C++] enum과 enum 클래스C++/Effective Modern C++ 2022. 4. 18. 13:37
C++98스타일의 enum은 C++의 기본 스코프 규칙이 적용되지 않습니다.
enum Color { black, white, red }; auto white = 0;
C2365 : 'white': 재정의: 이전 정의는 '열거자'입니다.
enum의 열거자들은 enum을 포함하는 범위에 속하여, 위와 같이 자신들의 이름이 범위 밖으로 나가는 것을 볼 수 있습니다.
C++에서는 새로이 enum class가 추가되었는데, 이번 글에서는 그에 대해 다루어 보도록 하겠습니다.
1. Scoped enum
더보기범위 있는 enum (Scopred enum)은 enum class를 부르는 말 입니다.
예제를 살펴보도록 하겠습니다.
enum class Color { black, white, red }; auto white = 0; //Color c = white; Color c = Color::white;
예제와 같이 enum class를 이용하면 열거자의 범위가 enum 으로 새어나가지 않습니다.
또한 주석과 같이 범위 밖에서 사용할 때 형식을 지정하지 않을 경우 컴파일이 되지 않습니다.
2. enum과 형 변환
더보기기존의 enum은 암묵적으로 정수형으로 변환되었습니다.
그렇기 때문에, 아래와 같은 이상한 코드가 유효합니다.
enum Color { black, white, red }; std::vector<std::size_t> primeFactors(std::size_t x){ ... } Color c = red; if (c < 14.5) { // ??? auto factors = primeFactors(c); // ??? }
Color의 의미에 맞지 않게, 정수와 비교하는 구문과, 정수로부터 특정 연산 (위 함수의 경우 소인수를 반환하는 함수라고 가정합니다)이 컴파일 됩니다.
enum class의 경우 다른 형식으로 암묵적으로 변환되지 않습니다.
예제는 첨부하지 않도록 하겠습니다. 위의 코드의 enum을 enum class로 바꿀 경우, 정수 혹은 실수로 형 변환이 되지 않기 때문에 컴파일이 거부됩니다.
특정한 목적으로 enum class를 형 변환 해야할 경우, 아래와 같이 사용할 수 있습니다.
if(static_cast<double>(c) < 14.5) { ... }
형 변환 연산자를 통해 원하는 형태로 변경할 수 있습니다.
3. enum의 선행 선언
더보기enum은 기본적으로 선언만 할 수 없습니다.
이것은 enum의 특징 때문인데, 컴파일러는 메모리의 효율을 위해 enum의 열거자 값들을 표현 가능한 가장 작은 형식을 선택하기 때문입니다.
하지만 이것이 단점을 낳기도 했습니다.
다음 enum을 가정해보도록 하겠습니다.
enum Status { OK = 200; BadRequest = 400; NotFound = 404; }
위와 같이 통신의 상태를 의미하는 enum을 만들었다고 가정하면 이것은 시스템에서 전반적으로 쓰일 가능성이 높을 것이며, 따라서 헤더 파일에 포함될 수 있을 것 입니다.
하지만 아래와 같이 새 열거자가 추가될 경우
enum Status { OK = 200; BadRequest = 400; Forbidden = 403; NotFound = 404; }
이 새 열거자가 일부 하위 시스템에서 사용된다고 하더라도 시스템 전체가 다시 컴파일 되어야 할 것입니다.
이런 경우는 enum을 헤더 파일에 선언만 하는 것으로 해결할 수 있습니다.
enum Status;
이렇게 선언만 하게 될 경우, 헤더를 다시 컴파일 할 필요가 없어집니다.
하지만, enum은 그 특성상 위와 같이 선언만 하는 것이 불가능합니다.
enum class는 컴파일러가 형식을 선택하지 않습니다. enum class의 기본 형식은 int입니다.
enum class Status;
따라서 위와 같이 선언만 하는 것이 가능합니다.
만약 정의 할 enum class의 열거자들이 int만으로 나타내기에 너무 클 경우, 형식을 직접 지정할 수 있습니다.
enum class Status: std::uint32_t; enum Color: std::uint8_t;
위와 같이 형식을 직접 지정하는 것은 enum class뿐만 아니라 enum도 가능합니다.
이 경우에는 enum 또한 형식이 정해지기 때문에 선언만 하는 것이 가능해집니다.
4. enum과 std::tuple
더보기enum의 전방 선언이 불가능하다는 문제는 형식을 지정하는 것으로 해결되었지만, 이것만으로는 enum은 enum class에 비해 사용하기 곤란합니다.
하지만 이것을 응용할 수 있는 상황이 존재합니다.
STL중 하나인 std::tuple에 관한 예제를 살펴보도록 하겠습니다.
//User Info : Name, Email, Reputation std::tuple<std::string, std::string, std::size_t> user_info; ... auto name = std::get<1>(user_info); ... enum user_info_fileds { u_name, u_email, u_reputation }; auto name = std::get<u_name>(user_info);
user_info라는 std::tuple객체는 이름, 이메일, 평판수치를 가지고 있는 객체입니다.
위 예제에서는 튜플 객체에서 이름을 탐색하는 두 가지 방법을 소개하고 있습니다.
두 방법 모두 std::get<>을 사용했지만, 첫 번째는 튜플의 인덱스를 이용했고
두 번째는 enum을 통해 인덱스와 이름을 매칭시킨 후 탐색했습니다.
enum을 사용함으로써, 사용자가 필드의 인덱스와 이름을 따로 기억해야 하는 불편함이 사라졌습니다.
이번에는 위 객체에서 enum class를 사용해 탐색하는 예제를 살펴보겠습니다.
enum class user_info_fields { u_name, u_email, u_reputation }; ... auto name = std::get<static_cast<std::size_t>(user_info_fields::u_name)>(user_info);
enum class의 경우 암묵적 변환이 허용되지 않기 때문에, enum보다 코드가 길어집니다.
여기서 코드를 간결하게 하기 위해 열거자를 받고, 그에 해당하는 std::size_t를 반환하는 함수를 작성한다고 가정하겠습니다.
5. enum과 std::tuple (2)
더보기enum class는 묵시적 형 변환이 허용되지 않기 때문에 위 문단의 예제에서 캐스팅을 해서 사용했습니다.
만약 enum class만 사용해야 하는 상황이라, 위와 같은 코드를 조금 더 간략하게 하기 위해 함수를 하나 작성한다고 가정하겠습니다.
그 함수는 열거자 하나를 받고, 그에 해당하는 std::size_t를 반환하는 함수 일 것입니다.
그러한 함수 to_user_type을 만들기 위해 생각해야 하는 사항들은 아래와 같습니다.
- 우선, 함수는 constexpr 함수 템플릿이어야 합니다.
- 함수의 반환형이 std::get의 함수 인수가 아닌, 템플릿 인수에 사용되었습니다.
- 템플릿 인수는 컴파일 과정에 산출되어야 합니다.
- 추가로, 다른 enum class에 대하여도 작동해야 합니다.
- 반환 형식이 std::size_t가 아닌 enum 기반의 형식 즉, std::underlying_type이 되어야 합니다
- 매개변수가 일반화 되었기 때문에, 반환 형식도 일반화 되어야 하기 때문입니다.
- 함수는 noexcept이어야 합니다.
- 예외가 없을 것이 확실하기 때문입니다
위의 항목들을 고려한 to_user_type은 아래와 같습니다.
template<typename E> constexpr auto to_user_type(E enumerator) noexcept { return static_cast<std::underlying_type_t<E>>(enumerator); } ... auto name = std::get<to_user_type(user_info_fields::u_name)>(user_info);
위와 같이 to_user_type을 정의하면 아래와 같이 enum class를 인덱스로 사용할 수 있습니다.
마지막 예제는 여전히 enum만을 사용할 때 보다 타자량이 많고, 읽기도 상대적으로 어렵습니다.
하지만 static_cast를 사용할 때 보다는 가독성이 나아졌고, 여러 enum class에 대하여 사용 가능하다는 장점이 있습니다.
enum을 사용한 간결한 표현과, enum class를 사용했을 때의 따라오는 부차적인 이점들 사이에서 적절한 방법을 선택해야겠지만, 대체로 후자가 더 이득인 경우가 많지 않을까 합니다.
감사합니다.
'C++ > Effective Modern C++' 카테고리의 다른 글
[Effective Modern C++] 식별자 : override (0) 2022.04.22 [Effective Modern C++] private와 delete (0) 2022.04.22 [Effective Modern C++] using과 typedef (0) 2022.04.05 [Effective Modern C++] 0과 NULL과 nullptr (0) 2022.03.30 [Effective Modern C++] 괄호 ()와 중괄호 {} 그리고 Uniform initialization (0) 2022.02.16 - 우선, 함수는 constexpr 함수 템플릿이어야 합니다.