TypeCast는 Modern C++ Design책에 나오는 내용이다. 필수 알아야 할 기능은 아니지만 해당 내용을 공부하면서 얻게 되는 지식들이 꽤 쓸만하다.
class Player
{
public:
virtual ~Player(){}
}
class Knight : public Player
{
public:
}
class Mage : public Player
{
public:
}
int main()
{
Player* p1 = new Knight();
Player* p2 = new Mage();
Knight* k1 = static_cast<Knight*>(p2);
}
Mage pointer인 p2를 Knight pointer로 캐스팅하면 당시 오류는 발생하지 않지만 실제로 메모리 참조가 잘못되고 있다. dynamic_cast를 사용하면 잘못 캐스팅되는 부분을 잡을 수 있지만, 속도가 느려서 잘 사용하지 않는다.
템플릿을 이용하여 이런 문제를 해결해보자.
TypeCast.h
#pragma region TypeList
template<typename... T>
struct TypeList;
template<typename T, typename U>
struct TypeList<T, U>
{
using Head = T;
using Tail = U;
};
template<typename T, typename... U>
struct TypeList<T, U...>
{
using Head = T;
using Tail = TypeList<U...>;
};
#pragma endregion
위 코드를 이해하기 위해 다음 코드를 살펴보자.
template<typename T, typename U>
struct TypeList
{
using Head = T;
using Tail = U;
}
int main()
{
TypeList<Mage, Knight>:: whoAmI;
TypeList<Mage, Knight>:: whoAmI2;
TypeList<Mage, TypeList<Knight, Archer>::Head whoAmI3;
TypeList<Mage, TypeList<Knight, Archer>::Tail::Head whoAmI4;
TypeList<Mage, TypeList<Knight, Archer>::Tail::Tail whoAmI5;
}
TypeList<Mage, TypeList<Knight, Archer>::Tail::Head whoAmI4; 여기서 whoAmI4 는 Knight 타입이다.
이를 일반화하여 만든 것이 template TypeList이다. 3번째 정의를 보면 위 형식과 같다는 것을 알 수 있다.
TypeList 개수를 알기 위한 Length를 정의하자.
TypeCast.h
...
#pragma region Length
template<typename T>
struct Length;
template<>
struct Length<TypeList<>>
{
enum { value = 0 };
};
template<typename T, typename... U>
struct Length<TypeList<T, U...>>
{
enum {value = 1 + Length<TypeList<U...>>::value};
};
#pragma endregion
int main()
{
int32 len1 = Length<TypeList<Mage, Knight>>::value;
int32 len2 = Length<TypeList<Mage, Knight, Archer>>::value;
}
enum 값은 컴파일 타임에 결정된다. 따라서 Length template value값은 컴파일 타임에 결정된다.
TypeList에서 원하는 index의 Type을 가져오는 기능을 만들어보자.
TypeCast.h
...
#pragma region TypeAt
template<typename TL, int32 index>
struct TypeAt;
template<typename Head, typename... Tail>
struct TypeAt<TypeList<Head, Tail...>, 0>
{
using Result = Head;
};
template<typename Head, typename... Tail, int32 index>
struct TypeAt<TypeList<Head, Tail...>, index>
{
using Result = typename TypeAt<TypeList<Tail...>, index - 1>::Result;
};
#pragma endregion
원하는 index가 0이 아니면 Head를 없애고 TypeAt을 재귀적으로 호출한다.
int main()
{
using TL = TypeList<Mage, Knight, Archer>;
TypeAt<TL, 0>::Result whoAmI6;
TypeAt<TL, 1>::Result whoAmI7;
TypeAt<TL, 2>::Result whoAmI8;
}
위와 반대로 타입의 index를 찾는 기능을 만들어 보자.
TypeCast.h
...
#pragma region IndexOf
template<typename TL, typename T>
struct IndexOf;
template<typename... Tail, typename T>
struct IndexOf<TypeList<T, Tail...>, T>
{
enum { value = 0 };
};
template<typename T>
struct IndexOf<TypeList<>, T>
{
enum { value = -1 };
};
template<typename Head, typename... Tail, typename T>
struct IndexOf<TypeList<Head, Tail...>, T>
{
private:
enum { temp = IndexOf<TypeList<Tail...>, T>::value };
public:
enum { value = (temp == -1) ? -1 : temp + 1 };
};
#pragma endregion
int main()
{
int32 index1 = IndexOf<TL, Mage>::value;
int32 index2 = IndexOf<TL, Archer>::value;
int32 index3 = IndexOf<TL, Dog>::value;
}
Type from에서 Type to로 변환이 가능한지 알아보는 기능을 만들어 보자.
TypeCast.h
...
#pragma region Conversion
template<typename From, typename To>
class Conversion
{
private:
using Small = __int8;
using Big = __int32;
static Small Test(const To&) { return 0; }
static Big Test(...) { return 0; }
static From MakeFrom() { return 0; }
public:
enum
{
exists = sizeof(Test(MakeFrom())) == sizeof(Small)
};
};
#pragma endregion
From이 To로 변환이 되면 static Small Test(const To&)를 사용하고 그렇지 않으면 static Big Test(...)을 사용하게 된다.
int main()
{
bool canConvert1 = Conversion<Player, Knight>::exists;
bool canConvert2 = Conversion<Knight, Player>::exists;
bool canConvert3 = Conversion<Knight, Dog>::exists;
}
다음으로 C# as, is와 같은 기능을 하는 템플릿 함수를 만들어 보자.
먼저 주어진 타입 리스트의 모든 type cast 가능 여부를 저장하는 테이블을 만든다.
...
#pragma region TypeCast
template<int32 V>
struct Int2Type
{
enum { value = V };
};
template<typename TL>
class TypeConversion
{
public:
enum
{
length = Length<TL>::value
};
TypeConversion()
{
MakeTable(Int2Type<0>(), Int2Type<0>());
}
template<int32 i, int32 j>
static void MakeTable(Int2Type<i>, Int2Type<j>)
{
using FromType = typename TypeAt<TL, i>::Result;
using ToType = typename TypeAt<TL, j>::Result;
if (Conversion<const FromType*, const ToType*>::exists)
s_convert[i][j] = true;
else
s_convert[i][j] = false;
MakeTable(Int2Type<i>(), Int2Type<j + 1>());
}
template<int32 i>
static void MakeTable(Int2Type<i>, Int2Type<length>)
{
MakeTable(Int2Type<i + 1>(), Int2Type<0>());
}
template<int32 j>
static void MakeTable(Int2Type<length>, Int2Type<j>)
{
}
static inline bool CanConvert(int32 from, int32 to)
{
static TypeConversion conversion;
return s_convert[from][to];
}
public:
static bool s_convert[length][length];
};
template<typename TL>
bool TypeConversion<TL>::s_convert[length][length];
...
함수 MakeTable에서 하고 싶은 동작은 runtime 일반 함수로 표현하면 다음과 같다.
TypeConversion()
{
for (int i=0; i<length; i++)
{
for (int j=0; j<length; j++)
{
using FromType = typename TypeAt<TL, i>::Result;
using ToType = typename TypeAt<TL, j>::Result;
if (Conversion<const FromType*, const ToType*>::exists)
s_convert[i][j] = true;
else
s_convert[i][j] = false;
}
}
}
public:
static bool s_convert[length][length];
i와 j는 런타임에 결정되므로 template에서 사용 불가능하다. 따라서 Int2Type struct를 이용한 것이다.
만든 type cast 테이블을 활용하는 템플릿 함수를 만들어 보자.
template<typename To, typename From>
To TypeCast(From* ptr)
{
if (ptr == nullptr)
return nullptr;
using TL = typename From::TL;
if (TypeConversion<TL>::CanConvert(ptr->_typeId, IndexOf<TL, remove_pointer_t<To>>::value))
return static_cast<To>(ptr);
return nullptr;
}
template<typename To, typename From>
shared_ptr<To> TypeCast(shared_ptr<From> ptr)
{
if (ptr == nullptr)
return nullptr;
using TL = typename From::TL;
if (TypeConversion<TL>::CanConvert(ptr->_typeId, IndexOf<TL, remove_pointer_t<To>>::value))
return static_pointer_cast<To>(ptr);
return nullptr;
}
template<typename To, typename From>
bool CanCast(From* ptr)
{
if (ptr == nullptr)
return false;
using TL = typename From::TL;
return TypeConversion<TL>::CanConvert(ptr->_typeId, IndexOf<TL, remove_pointer_t<To>>::value);
}
template<typename To, typename From>
bool CanCast(shared_ptr<From> ptr)
{
if (ptr == nullptr)
return false;
using TL = typename From::TL;
return TypeConversion<TL>::CanConvert(ptr->_typeId, IndexOf<TL, remove_pointer_t<To>>::value);
}
#pragma endregion
...
remove_pointer_t<T>는 T가 포인터이면 포인터를 떼어버린 타입을 반환한다.
static_pointer_cast<T>는 스마트 포인터 형변환 때 사용한다.
지금까지 만든 기능을 사용하기 위해 2개의 선언을 만든다.
...
#define DECLARE_TL using TL = TL; int32 _typeId;
#define INIT_TL(Type) _typeId = IndexOf<TL, Type>::value;
클래스 선언을 다음과 같이 하고 사용하면 된다.
using TL = TypeList<class Player, class Mage, class Knight, class Archer>;
class Player
{
public:
Player()
{
INIT_TL(Player);
}
virtual ~Player() { }
DECLARE_TL
};
class Knight : public Player
{
public:
Knight() { INIT_TL(Knight); }
};
class Mage : public Player
{
Mage() { INIT_TL(Mage); }
};
class Archer : public Player
{
Archer() { INIT_TL(Archer); }
};
int main()
{
{
Player* player = new Knight();
bool canCast = CanCast<Knight*>(player);
Knight* knight = TypeCast<Knight*>(player);
delete player;
}
{
shared_ptr<Knight> knight = MakeShared<Knight>();
shared_ptr<Player> player = TypeCast<Player>(knight);
bool canCast = CanCast<Player>(knight);
}
}
1. TypeList 어떻게 동작하는지?
- 첫 번째 부분 : template 부분에 어떠한 타입도 들어갈 수 있다는 의미.
첫 번째 부분은 동작하지 않음. 템플릿 특수화 사용하기 위해 정의한 것.
- 두 번째 부분 : template 부분에 2 개의 타입이 들어왔을 때 동작.
- 세 번째 부분 : template 부분에 3 개 이상의 타입이 들어왔을 때 동작.
2. Length 어떻게 동작하는지?
- 첫 번째 부분 : 템플릿 특수화 이용 위함
- 두 번째 부분 : TypeList 가 비어있으면 0으로 값 세팅
- 세 번째 부분 : 두 번째 부분 갈 때까지 1씩 증가 시키면서 lengh 계산.
3. TypeAt 어떻게 동작하는지?
- 1 처럼 Tail, Head 를 사용하여 접근하는 부분을 개선하기 위함.
- 2 처럼 세 번째 부분이 일반적인 부분, 두 번째 부분이 마지막 부분임.
- 두 번째 부분 : 세 번째 부분에서 index 가 0 일 때, Head를 Result로 세팅.
- 세 번째 부분 : Type List에서 Head를 하나씩 빼고, index도 1 씩 줄이면서, Result 계산.
4. IndexOf 어떻게 동작하는지?
- 두 번째 부분 : 찾으려는 타입이 Head인 경우 0 으로 값 세팅.
- 세 번째 부분 : 찾으려는 타입 없으면 -1 으로 값 세팅.
- 네 번째 부분 : Head를 하나씩 제거하면서 값은 1 늘리면서, 찾으려는 타입 찾으면 해당 값으로 세팅.
5. Conversion 어떻게 동작하는지?
- MakeFrom 리턴 값 From이 To로 변환 되면 Test가 Small을 반환, 그렇지 않으면 Big을 반환하는 로직을 이용함.
6. TypeConversion 에서 MakeTable이 런타임 변수 i, j를 해결하기 위해 사용한 방법은?
- template 구조체 IntToType을 정의하여 컴파일에 i, j마다 각각 다른 구조체를 만들게 함.
- IntToType<T> 에서 T를 length 로 받는 특수 template 함수 MakeTable 을 만들어 재귀 탈출함.
출처 : [C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버 - 인프런 | 강의 (inflearn.com)
소켓 프로그래밍 기초 #2 (0) | 2021.11.04 |
---|---|
소켓 프로그래밍 기초 #1 (0) | 2021.11.03 |
Object pool (0) | 2021.10.01 |
Memory pool #3 (0) | 2021.09.30 |
Memory pool #2 (0) | 2021.09.23 |
댓글 영역