bdfgdfg

[C++11] using, enum class 본문

게임프로그래밍/C++

[C++11] using, enum class

marmelo12 2022. 1. 22. 23:11
반응형

using

c++11에서 추가된 typedef대신 사용할 수 있는 키워드

typedef을 사용하면 사용자가 여러 타입의 별칭을 생성하고, 사용할 때에는 해당 별칭을 통해 사용할 수 있다.

 -> 참조 키워드에서 해당 메모리에 다른 별칭을 붙이는 것과 비슷한 개념.

 -> C/C++에서 이미 정의된 자료형이나 사용자 정의 자료형을 사용자가 원하는 별칭을 붙여 사용 가능하다.

 

typedef이 사용될 수 있는 간단한 예제를 보면

1
2
3
4
5
6
7
8
9
10
11
typedef std::list<int>::iterator ListPos;
 
int main()
{
    std::list<int> list;
    list.push_back(100);
 
    ListPos iter = list.begin();
    std::cout << *iter << std::endl;
    return 0;
}
cs

위와같이 타입이 긴 자료형을 줄이기 위해서 사용되거나.

위와 같이
 
 
 
 
 
 
 
 
 
 
 
 
 
 

가독성을 살리기 위해 typedef을 사용할 수도 있다. 또한 장점은 만약 playerID가 int로는 더 이상 담지 못한다고 했을 때 위의 typedef선언 부분만 찾아가 int64로 바꾸는 등. 모든 코드를 바꿀 필요 없이 간편하게 사용할 수 있다.

 

using키워드도 기본적으로 typedef의 기능을 한다.

1
2
3
4
5
typedef std::list<int>::iterator ListPos;
using ListPos = std::list<int>::iterator;
 
typedef int ID;
using ID = int;
cs

using의 경우에는 새로운 타입을 선언하고 그 뒤에 대입연산자를 통해 사용할 자료형을 넣어주면 된다.

 

먼저 장점으로는 typedef보다 좀 더 직관성이 높아졌다.

 -> C++11이전의 typedef 함수 포인터 별칭을 지금까지 밑과 같이 정의했었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef void (*MyFunction)(int,int);
 
void TestFunction(int a, int b)
{
    int c = a + b;
    std::cout << c;
}
 
int main()
{
    MyFunction a = TestFunction;
 
    a(3040);
    return 0;
}
cs

반환형이 void이고 int형 매개변수 2개를 받는 함수의 함수 포인터의 별칭을 MyFunction이라 선언한 것.

하지만 가독성면에서는 그다지 좋지는 않다.

using 키워드를 사용하면

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
typedef void (*MyFunction)(int,int);
//using
using MyFunction2 = void(*)(intint);
 
void TestFunction(int a, int b)
{
    int c = a + b;
    std::cout << c;
}
 
int main()
{
    MyFunction a = TestFunction;
    a(3040);
    MyFunction2 b = TestFunction;
    b(3040);
    return 0;
}
 
cs

위와같이 대입 연산자를 기준으로 왼쪽에는 함수 포인터의 별칭, 오른쪽은 함수 포인터의 원형을 나타낸다.

 -> 가독성 ↑

 

using은 직관성도 이유가 되지만 template과 연관되어있다.

typedef은 템플릿을 활용할 수 없다. (문법적으로 불가능)

1
2
3
4
5
6
7
8
9
10
11
12
template <typename T>
typedef std::unordered_map<T> Hash_map; // 불가능
 
template <typename T>
using Hash_map2 = std::unordered_map<T>// 가능
 
 
int main()
{
    Hash_map2<int> a;
    return 0;
}
cs

위와 같이 using은 template선언을 사용하여 별칭을 붙일 수 있지만 typedef은 불가능하다.

다만 한번 struct와 같은 구조체를 한번 래핑 하여 사용할 수 있는데

1
2
3
4
5
6
7
8
9
10
11
12
template <typename T>
struct Map
{
    typedef std::unordered_map<T> Hash_map;
};
 
 
int main()
{
    Map<int>::Hash_map map;
    return 0;
}
cs

이처럼 Map이라는 struct로 한번 감싸주어 그 안에 템플릿의 별칭을 붙이는 것은 가능하다.

다만 using이 사용 가능한데 이렇게 사용할 이유는 이제는 없다.

 

 

enum class

enum은 열거형으로 많이 사용되었다.

1
2
3
4
5
6
enum RGB
{
    RED     = 0xFF0000,
    GREEN   = 0x00FF00,
    BLUE    = 0x0000FF
};
cs

위와 같이 열거형을 사용하면 변수가 갖는 '값'의미를 부여할 수 있어 가독성을 높일 수 있다.

 -> typedef이나 using은 자료형을 대상으로.

그 외에도 비트 마스크를 활용할 때에도 enum을 사용하면 편리한데 (RGB도 비트 마스크 개념 적용)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
enum CharacterInfo
{
    // 2의 n승씩 증가. (비트마스크 활용)
    HeadEquip   = 1,
    BodyEquip   = HeadEquip * 2// 혹은 1 << 1
    GlovesEquip = BodyEquip * 2// 혹은 1 << 2
    ShoesEquip  = GlovesEquip * 2 // 혹은 1 << 3
};
 
int main()
{
    int player = 0;
 
    // 가독성을 위해 Player클래스가 존재한다면
    // 해당 클래스에서 밑과 같은 기능을 감싸 사용하는게 좋다(캡슐화,은닉성)
    player |= BodyEquip; // 장비착용
    player |= GlovesEquip; // 장갑착용
 
    player &= ~(BodyEquip); // 장비해제.
 
    if (player & (BodyEquip))
        std::cout << "장비 착용" << std::endl;
    else if (player & GlovesEquip)
        std::cout << "장갑 착용" << std::endl;
    
    return 0;
}
cs

위 코드처럼 Player의 장비 착용 정보를 bool 변수를 여러 개를 놔둘 필요 없이 비트를 활용하여 메모리를 절약시킬 수 있다.

특히 enum은 switch문을 사용할 때 더욱 직관적인 사용이 가능하기에 자주 사용한다.

다만 enum은 위 코드에서 선언한 CharacterInfo정의 부분인 중괄호 안에서만 적용되는 건 당연히 아니기에

enum의 열거형 값과 사용자가 정의할 클래스나 구조체의 이름과 겹치면 나중에 사용할 땐 열거형값으로 먼저 인식하기에 사용할 수가 없다. 

 


1
2
3
4
5
6
7
8
enum CharacterInfo
{
    // 2의 n승씩 증가. (비트마스크 활용)
    CI_HeadEquip   = 1,
    CI_BodyEquip   = CI_HeadEquip * 2// 혹은 1 << 1
    CI_GlovesEquip = CI_BodyEquip * 2// 혹은 1 << 2
    CI_ShoesEquip  = CI_GlovesEquip * 2 // 혹은 1 << 3
};
cs

그렇기에 적어도 열거형 값을 정의할 땐 enum의 이름을 따 열거형 값의 이름 앞에 붙여준다.

이러한 enum을 다른 이름으로 unscoped enum (범위 없는 열거형)이라고도 한다.

 

새로 등장한 enum class는 위와 같은 문제를 막았다.

다른 말로 scoped enum이라 하며 enum class안에 정의된 열거형 값은 해당 enum class 공간 을 통해서만 접근이 가능하다. 또한 enum과 달리 암묵적인 변환을 막는다는 특징이 존재한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum class MonsterType
{
    None,
    Goblin,
    Orc
};
 
int main()
{
    int a = None; // 접근 불가능
    int b = MonsterType::Goblin; // 암묵적인 변환 불가능. (enum이었으면 가능)
    int c = static_cast<int>(MonsterType::Orc); // 변환 후 사용.
    return 0;
}
cs

 

이처럼 일반적인 enum보다는 사용이 불편해졌지만, 이름이 겹칠 우려가 없다.(이름공간 관리)

다만 enum과 달리 암묵적인 변환을 해주지 않기에 캐스팅을 해줘야 한다. enum은 이름을 정해주고 값을 정해주지 않으면 기본적으로 0부터 시작해서 1씩 증가하는데 이건 enum class도 마찬가지.

이러한 암묵적인 변화가 불가능한것이 장점이 될수도 있지만 단점이 될수도 있다.

 -> 해당 enum Class 타입으로만 사용하면 상관없지만 정수형으로 열거형 값을 가져다가 쓸려면 매번 캐스팅을 해줘야한다는 불편함이 존재한다.

특히 비트 마스크를 enum class를 통해 사용한다면

1
2
3
4
unsigned int bitMask = 0;
 
bitMask |= HeadEquip; // 불가능
bitMask != static_cast<unsigned int>(CharacterInfo::HeadEquip);
cs

위와 같이 매우 귀찮게 캐스팅을 하여 사용해야 한다.

 

그렇기에 enum class의 경우에는 무조건 이 문법을 사용해야 한다는 것은 아니고 사용자의 편리성에 따라 적절히 선택해서 사용하면 된다.

반응형
Comments