2017년 3월 6일 월요일

C++ Unscoped enumerations VS. Scoped enumerations

 C++11에 새로 추가된 것들 중에 enum class라는 것이 있다. 원래부터 있었던 enum과 유사하지만 좀 더 발전된 것이라고 할 수 있겠다. Effective Modern C++에서는 원래 있던 enum을 unscoped enum으로 새로 추가된 enum class를 scoped enum으로 구별해서 부른다. 선언 방식은 아래 예와 같다.
enum Color { black, white, red }; // unscoped enum
int white; // 에러
enum class Color { black, white, red }; // scoped enum
int white; // 문제 없음
 선언 방법에 있어서 scoped enum의 경우 "enum class"를 사용한다는 점이 이전과 달라진 점이다. 두 가지 방식을 scoped와 unscoped로 부르는 이유는 말 그대로 black, white, red의 namespace 범위가 두 경우에 대해 다르게 적용되기 때문이다. 위 예를 가지고 설명해 보자면 unscoped의 경우에는 black, white 등의 이름은 Color와 같은 namespace를 갖는다. 즉, Color라는 이름이 유효한 공간에서는 black, white 등의 이름도 모두 유효하다. 따라서 다른 변수나 함수 이름 등으로 사용할 수가 없다. 하지만 scoped의 경우에는 black, white 등의 이름이 Color 클래스 내부에서만 유효하다. 즉, Color의 중괄호 {} 안에서만 유효하므로 그 밖에서는 그 이름을 다른 변수나 함수 이름으로 사용하는 데 아무런 문제가 없다. 물론 enum값을 사용할 때 Color::black과 같이 사용해야 하는 게 불편할 수도 있지만 내가 보기엔 더 명확하고 좋은 것 같다.

 사실 이렇게 namespace가 오염(?)된다는 것만으로 scoped enum을 사용하라고 한다면 그다지 크게 공감이 가진 않는다. Effective Modern C++에서는 몇 가지 장점을 더 제시하고 있다. 그 중 한 가지가 scoped enum의 경우에는 전방 선언(forward declaration)이 가능하다는 것이다. unscoped enum의 경우에는 전방선언을 할 수 없었지만 scoped enum의 경우 이게 가능해지면서 소스 파일간의 dependency를 줄여서 헤더 파일을 약간 수정함으로써 생기는 많은 소스 코드의 재컴파일을 줄일 수 있다는 장점이 있다. C++로 정말 큰 프로젝트를 수행한다면 빌드 타임을 줄일 수 있다는 점은 큰 메리트가 될 것이다. 하지만 사실 이 점도 나에게는 그렇게 크게 공감가는 부분은 아니다. 대부분 큰 프로젝트를 수행하다보면 어느 정도 작은 크기의 모듈로 나누어지게 마련이고 그러다보면 한 번 빌드하는데 5분이상 걸리는 경우를 만나는 일도 별로 없기 때문이다. 게다가 unscoped enum도 약간의 수고를 더한다면 전방선언이 가능하기 때문에 별로 큰 장점은 아니다. 이 글을 계속 읽어보면 전방선언이 가능하다는 걸 알게 될 것이다.

 Effective Modern C++이 제시하는 장점 중에 내가 생각하는 가장 큰 장점은 타입 체크를 더 강하게 한다는 것이다. Java 같은 언어에서 enum의 타입은 class와 동등하게 엄격한 타입 검사가 적용된다. 하지만 C++에서 (unscoped) enum은 그냥 정수형처럼 사용해도 아무 제지를 받지 않는다. 심지어 float같은 상수와 연산을 해도 암묵적 변환이 자동으로 이루어진다. 물론 이게 편하다고 생각하는 개발자도 있겠지만 적어도 바람직하지 않은 것만은 사실이다. 하지만 C++11에서 추가된 scoped enum의 경우에는 정수형으로의 암묵적 변환이 허용되지 않는다. 필요하다면 개발자가 암묵적이 아닌 직접적인 캐스팅을 해주어야 문제가 없다. 이런 방식이 개발자의 실수를 줄이고 이해하기 쉬운 코드를 만드는데 훨씬 유리하다고 나는 확신한다. Java같은 최신 언어들로부터 느낀 많은 장점들이 C++에도 조금씩 조금씩 적용된다는 건 바람직한 일이 아닐 수 없다.

 많은 개발자들이 신경쓰지 않고 사용했겠지만 enum을 선언할 때 개발자가 직접 기초 타입(underlying type)을 지정할 수 있다. 아래 예에서 보듯이 unscoped와 scoped 둘 다 지정이 가능하다.
 // unscoped enum
enum Status: std::uint32_t { good = 0,
                             failed = 1,
                             incomplete = 100,
                             corrupt = 200,
                             audited = 500,
                             indeterminate = 0xFFFFFFFF
                           };

// scoped enum
enum class Status: std::uint32_t { good = 0,
                                   failed = 1,
                                   incomplete = 100,
                                   corrupt = 200,
                                   audited = 500,
                                   indeterminate = 0xFFFFFFFF
                                 };

 위의 예처럼 기초 타입을 직접 지정하는 경우에는 unscoped enum의 경우에도 전방 선언이 가능하다. 전방 선언이 허용되지 않았던 이유는 enum의 기초 타입이 지정되지 않았을 때는 컴파일러가 enum의 정의를 보고 적당한 크기의 정수형(integral) 타입을 임의로 결정하여 사용하기 때문에 기초 타입이 지정되지 않은 전방 선언 만으로는 그 크기를 알 수 없으므로 전방선언을 허용하지 않았던 것이다. 하지만 scoped enum의 경우에는 직접 지정하지 않았을 때 default로 int형을 사용하게 되어있다. 더 작거나 큰 타입을 사용하고 싶다면 직접 지정해서 사용하면 된다.

 참고로 unscoped enum이 더 편리한 경우를 들어보자면 array나 std::tuple의 인덱스로 사용할 정수형 상수(constant)를 enum으로 선언해놓고 사용할 때이다. 이 경우에는 casting이 필요없는 unscoped enum이 더 사용하기 편리하다. 아래 두 버전의 차이를 보자.
using UserInfo = std::tuple<std::string,  // name
                            std::string,  // email
                            std::size_t>; // reputation

// unscoped enum
enum UserInfoFields { uiName, uiEmail, uiReputation };

UserInfo uInfo;

...

auto val = std::get<uiEmail>(uInfo); // emil 필드의 값 얻기
// scoped enum
enum class UserInfoFields { uiName, uiEmail, uiReputation };

UserInfo uInfo;

...

auto val = std::get<static_cast<std::size_t>(UserInfoFields::uiEmail)>(uInfo); // 캐스팅 필요
 굳이 추가 설명을 하지 않아도 unscoped 버전이 훨씬 가독성이 좋고 타이핑해야 할 코드도 짧다. 게다가 이런 식의 사용은 많은 개발자들이 애용하는 방법이기때문에 unscoped enum의 장점을 무시할 수도 없다.

 unscoped enum과 scoped enum을 비교했을 때 정말 치명적인 장단점이 있다고는 생각치 않는다. unscoped enum이 전혀 불편함이 없고 익숙하다면 뭐 그리 대단히 사용하지 말아야 할 이유가 있는 건 아니라는 뜻이다. 하지만 내 의견은 scoped enum을 사용하는 것이 개발자의 실수를 줄이고 코드의 의도를 더 분명히 할 수 있는 더 좋은 습관이라고 생각한다. 그래서 C++표준에 추가된 것일 테고 새로운 세대의 개발자들이 scoped enum을 사용해주길 기대하고 있을 거라 생각한다.

0 개의 댓글:

댓글 쓰기