🔍 배경: 왜 직접 TypeCast를 구현할까?

우리는 게임 서버에서 다양한 유닛(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의 안전성
    이 두 가지를 템플릿 메타프로그래밍(TMP) 기법을 사용해 동시에 얻는 TypeCast 시스템을 구현합니다.

1️⃣ 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...>;
};

예시:

using TL = TypeList<Player, Mage, Knight, Archer>;

TL::Head who1;               // Player
TL::Tail::Head who2;         // Mage
TL::Tail::Tail::Head who3;   // Knight

2️⃣ Length

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

3️⃣ TypeAt

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;
};

4️⃣ IndexOf

주어진 타입이 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) };
};

5️⃣ Conversion

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)
    };
};

6️⃣ TypeConversion

모든 타입 간 캐스팅 가능 여부를 테이블로 생성합니다.

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];

7️⃣ TypeCast / CanCast

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 버전도 동일하게 작성됩니다.


8️⃣ 사용 방법

#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); }
};

9️⃣ 예제 코드

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타입의 인덱스를 계산
ConversionFrom → To 캐스팅 가능 여부 판단
TypeConversion모든 타입 간 캐스팅 가능 여부를 테이블화
TypeCast / CanCast캐스팅 수행 or 가능 여부 반환
shared_ptr 버전static_pointer_cast 활용 가능

profile
李家네_공부방

0개의 댓글