C++ 프로젝트에서 상태값이나 타입을 정의할 때 enum은 거의 필수적으로 사용됩니다.
하지만 프로젝트 규모가 커질수록 enum을 어떻게 관리하느냐가 코드의 가독성과 유지보수성에 직접적인 영향을 줍니다. 이번 글에서는 전통적인 방식에서 시작해 C++11의 변화, 그리고 실무에서 자주 사용하는 절충안까지 흐름으로 정리해 보겠습니다.
1. 전통적인 C 스타일 enum
가장 단순하고 직관적인 방식입니다.
enum MonsterType
{
Slime,
Skeleton,
Boss,
Max
};
초기에는 문제가 없어 보이지만, 프로젝트가 커지면 열거자 이름이 전역 공간으로 노출되면서 충돌 위험이 생깁니다.
예를 들어 다른 시스템에서도 비슷한 이름을 사용하게 됩니다.
enum ItemType
{
Potion,
Scroll,
Boss, // 몬스터 보스가 아닌 아이템 보스 등급
Max
};
이처럼 서로 다른 의미인데도 이름이 겹치면 컴파일 에러가 발생합니다.
그 이유는 C 스타일 enum의 열거자들이 이름 공간에 묶여 있지 않기 때문입니다.
즉 컴파일러 입장에서는 다음과 비슷하게 보입니다.
const int Slimes = 0;
const int Skeleton = 1;
const int Boss = 2;
const int Max = 3;
const int Potion = 0;
const int Scroll = 1;
const int Boss = 2; // 동일한 이름, 컴파일 에러
const int Max = 3; // 동일한 이름, 컴파일 에러
이 문제는 파일이 많아지고 시스템이 분리될수록 실제로 자주 발생합니다.
2. C++11 의 해결책 : enum class
이름 충돌 문제를 근본적으로 해결하기 위해 C++11에서 enum class가 도입되었습니다.
enum class MonsterType
{
Slime,
Skeleton,
Boss,
Max
};
이제 열거자는 반드시 소속을 명시해야 합니다.
MonsterType monsterType = MonsterType::Slime;
이 방식은 이름 충돌을 완전히 차단하고 타입 안전성도 제공합니다.
하지만 실무에서 자주 부딪히는 지점이 있습니다.
배열 인덱스로 사용할 때나 사용시에 타입 캐스팅을 해야 한다는 점입니다.
MonsterData monsterTable[static_cast<int>(MonsterType::Max)];
// 또는 간단히 (int) 로 타입캐스팅
monsterTable [(int)MonsterType::Slime].hp = 100;
monsterTable [static_cast<int>(MonsterType::Slime)].hp = 100;
결국 enum class 는 정수처럼 사용할 수 없기 때문에 어떤 방식이든 타입 캐스팅이 필요합니다.
코드가 길어지는 것 자체보다, 반복 사용 시 가독성이 흐트러진다는 점이 더 크게 느껴질 수 있습니다.
3. 실무 절충안 : namespace + 익명 enum
namespace 를 선언 후, 이름없는 enum 으로 나열합니다.
enum 다음에 열거형 명칭을 써도 되지만 예를 들어, MonsterType::Type::Slime 처럼 오히려 코드가 더 조악해지기 때문에 굳이 쓸 필요는 없습니다.
namespace MonsterType
{
enum
{
Slime = 0,
Skeleton,
Boss,
Max
};
}
사용은 매우 단순합니다.
MonsterData mosterTable[ MonsterType::Max];
mosterTable[ MonsterType::Slime].hp = 50;
mosterTable[ MonsterType::Skeleton].hp = 150;
캐스팅 없이 정수 인덱스로 바로 사용할 수 있고, namespace 가 이름 충돌을 막아 줍니다.
코드를 읽을 때도 의도가 직관적으로 들어옵니다.
enum 값은 컴파일을 거치면, int 로 취급되기 때문에 변수로 저장할 필요가 있을 때는 int 변수로 대입하면 됩니다.
다만, currentLevel 에 Level::max 에 존재하지 않는 값도 넣을 수가 있기 때문에 주의가 필요합니다.
namespace Level
{
enum
{
Baby,
Veteran,
Nightmare,
max
};
}
int currentLevel = Level::Baby;
// 주의
// 컴파일은 통과하지만, 사용시 문제 발생
currentLevel = 10;
또한 using namespace 는 피해야 합니다.
using namespace MonsterType ;
이렇게 하면 다시 전역으로 이름이 퍼지면서 충돌 위험이 생깁니다.
마무리
enum 은 단순한 문법 요소처럼 보이지만, 프로젝트가 커질수록 코드 구조와 충돌 관리에 직접적인 영향을 줍니다.
전통 enum 은 단순하지만 이름 충돌 위험이 있고,
enum class 는 안전하지만 정수처럼 사용할 수 없어 캐스팅이 필요합니다.
namespace + 익명 enum 방식은 실무에서 자주 쓰이는 절충안으로, 충돌을 막으면서도 간결한 사용성을 제공합니다.
'C++' 카테고리의 다른 글
| [C++] 순수 가상 함수, 추상 클래스, 그리고 인터페이스 (0) | 2026.02.27 |
|---|---|
| [C++] static 완전히 이해하기 (0) | 2026.02.25 |
| [C++] 헤더 파일에서 Namespace와 const를 사용해 설정값 관리하기 (0) | 2026.02.18 |
| [C++] 값 복사는 무조건 느리다는 편견과 현대적 기준 (0) | 2026.02.15 |
| 참조 반환(Return by Reference)의 이해와 활용 (0) | 2026.01.27 |