우리는 게임 서버에서 다양한 유닛(Player, Knight, Mage, Archer)을 다룰 때 보통 상위 클래스 포인터로 통일해 관리합니다.
Player* p1 = new Knight();
이렇게 관리하면 특정 타입으로 캐스팅해서 사용할 일이 생깁니다.
Knight* k1 = static_cast<Knight*>(p1); // 위험한 캐스팅
하지만 static_cast는 잘못된 캐스팅임에도 불구하고 캐스팅이 성공해버리기 때문에 메모리 오염이 발생할 수 있습니다.
Knight* k2 = static_cast<Knight*>(p2); // p2는 Mage였음!
dynamic_cast는 이를 방지하고 nullptr을 반환하지만 성능 저하가 크므로 서버에서는 사용이 꺼려집니다.
static_cast의 성능dynamic_cast의 안전성다양한 타입을 리스트 형태로 저장하는 구조입니다.
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...>;
};
예시:
using TL = TypeList<Player, Mage, Knight, Archer>;
TL::Head who1; // Player
TL::Tail::Head who2; // Mage
TL::Tail::Tail::Head who3; // Knight
TypeList의 길이를 재귀적으로 계산합니다.
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 };
};
int len = Length<TypeList<Mage, Knight, Archer>>::value; // 3
TypeList의 특정 인덱스에 있는 타입을 가져옵니다.
template<typename TL, int index>
struct TypeAt;
template<typename Head, typename... Tail>
struct TypeAt<TypeList<Head, Tail...>, 0> {
using Result = Head;
};
template<typename Head, typename... Tail, int index>
struct TypeAt<TypeList<Head, Tail...>, index> {
using Result = typename TypeAt<TypeList<Tail...>, index - 1>::Result;
};
주어진 타입이 TypeList에서 몇 번째에 위치하는지 반환합니다.
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) };
};
From 타입이 To 타입으로 캐스팅 가능한지를 판단합니다.
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)
};
};
모든 타입 간 캐스팅 가능 여부를 테이블로 생성합니다.
template<int32 v>
struct Int2Type {
enum { value = v };
};
template<typename TL>
class TypeConversion {
public:
enum { length = Length<TL>::value };
static bool s_convert[length][length];
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;
s_convert[i][j] = Conversion<const FromType*, const ToType*>::exists;
MakeTable(Int2Type<i>(), Int2Type<j + 1>());
}
template<int32 i>
static void MakeTable(Int2Type<i>, Int2Type<length>) {
MakeTable(Int2Type<i + 1>(), Int2Type<0>());
}
template<int j>
static void MakeTable(Int2Type<length>, Int2Type<j>) {}
static bool CanConvert(int from, int to) {
static TypeConversion converter;
return s_convert[from][to];
}
};
template<typename TL>
bool TypeConversion<TL>::s_convert[Length<TL>::value][Length<TL>::value];
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>
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);
}
🔁 shared_ptr 버전도 동일하게 작성됩니다.
#define DECLARE_TL using TL = TL; int32 _typeId;
#define INIT_TL(Type) _typeId = IndexOf<TL, Type>::value;
using TL = TypeList<Player, Mage, Knight, 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 {
public:
Mage() { INIT_TL(Mage); }
};
class Archer : public Player {
public:
Archer() { INIT_TL(Archer); }
};
shared_ptr<Player> player = MakeShared<Knight>();
bool canCast1 = CanCast<Knight>(player); // true
bool canCast2 = CanCast<Mage>(player); // false
shared_ptr<Knight> knight = TypeCast<Knight>(player); // OK
shared_ptr<Archer> archer = TypeCast<Archer>(player); // nullptr
| 기능 | 설명 |
|---|---|
TypeList | 타입을 리스트로 저장 |
Length | 타입 리스트의 길이 계산 |
TypeAt | 특정 인덱스의 타입 반환 |
IndexOf | 타입의 인덱스를 계산 |
Conversion | From → To 캐스팅 가능 여부 판단 |
TypeConversion | 모든 타입 간 캐스팅 가능 여부를 테이블화 |
TypeCast / CanCast | 캐스팅 수행 or 가능 여부 반환 |
shared_ptr 버전 | static_pointer_cast 활용 가능 |