C++ RTTI 완벽 이해: dynamic_cast의 원리와 한계 – 커스텀 RTTI까지 (1편)

🚀 들어가며

C++을 공부하다 보면 한 번쯤 이런 상황을 만나게 됩니다.

Base* obj = GetObject();

Derived* derived = dynamic_cast<Derived*>(obj);
if (derived)
{
    // Derived 타입일 때만 처리
}

이 코드는 너무 익숙하지만,
면접에서 이런 질문을 받으면 답변하기가 막막합니다.

dynamic_cast는 내부적으로 어떻게 동작할까?
❓ 비용은 어느 정도일까?
❓ 상용 게임 엔진은 왜 dynamic_cast를 사용하지 않고 커스텀 RTTI를 사용할까?

이번 글에서는 C++의 RTTI의 시작점인 C++ dynamic_cast의 개념과 동작 원리, 그리고 한계까지 정리합니다.


🧠 RTTI란 무엇인가?

RTTI는 Run-Time Type Information의 약자입니다.

👉 쉽게 말하면:

“프로그램 실행 중에 객체의 실제 타입을 알아내는 기능” 입니다.

C++에서는 대표적으로 아래 기능들이 RTTI를 사용합니다:


🔍 dynamic_cast 기본 사용

가장 기본적인 사용 예제를 보겠습니다.

#include <iostream>

class Base
{
public:
    virtual ~Base() {}
};

class Derived : public Base
{
public:
    void Foo() { std::cout << "Derived::Foo\n"; }
};

int main()
{
    Base* base = new Derived();

    Derived* derived = dynamic_cast<Derived*>(base);

    if (derived)
    {
        derived->Foo();
    }

    delete base;
}

✔ 핵심 포인트

  • 업캐스팅(Base ← Derived): 항상 안전 (RTTI 필요 없음)
  • 다운캐스팅(Derived ← Base): RTTI 필요

👉 dynamic_cast는 다운캐스팅 시 타입 검증을 수행합니다.


⚙️ dynamic_cast가 필요한 이유

아래 예제와 같이 그냥 static_cast를 쓰면 안 될까요?

Derived* derived = static_cast<Derived*>(base);

👉 이 코드는 컴파일은 되지만 위험합니다.

만약 실제 객체가 Derived가 아니라면?

👉 정의되지 않은 동작 (Undefined Behavior)이 발생합니다.

반면 dynamic_cast는:

Derived* derived = dynamic_cast<Derived*>(base);

👉 형변환 실패 시:

👉 즉, 안전한 캐스팅이 가능합니다.


🧩 dynamic_cast 내부 동작 (핵심)

dynamic_cast는 단순한 캐스팅이 아닙니다.

👉 내부적으로는:

  1. 객체의 vtable을 통해
  2. type_info 구조체에 접근하고
  3. 현재 타입과 대상 타입을 비교합니다

즉: “실행 중에 타입을 확인하는 과정”이이 반드시 들어갑니다.”


⚠️ dynamic_cast의 비용은 언제 문제가 될까?

dynamic_cast는 안전한 캐스팅을 위해 런타임 검사를 수행합니다.

내부적으로는 객체의 실제 타입 정보를 확인하고,
필요한 경우 상속 계층을 따라 타입 변환이 가능한지 검사합니다.

이러한 런타임 타입 검사는 성능에 영향을 줄 수 있습니다.

물론 dynamic_cast가 “항상 느리기 때문에 쓰면 안 되는 기능”은 아닙니다.
dynamic_cast가 처음 나올 당시에는 어느 정도 맞는 얘기였지만,
컴파일러의 발전으로 일반적인 애플리케이션 코드에서
가끔 타입을 확인하는 정도라면 dynamic_cast의 비용은 크게 문제가 되지 않는 경우가 많습니다.

문제가 되는 경우는 주로 게임 엔진처럼
매 프레임 수백, 수천 개의 객체를 반복 처리해야 하는 경우입니다.

예를 들어 Tick, Update, Collision, Render 준비 단계처럼
프레임마다 반복 호출되는 함수에서 dynamic_cast가 계속 사용된다면
그 비용이 누적될 수 있습니다.

즉, dynamic_cast의 문제는 “한 번 호출했을 때 너무 느리다”가 아니라,
“반복 호출되는 엔진 루프 안에서 사용되면 성능에 문제를 줄 수 있다”는 점에 가깝습니다.


🧪 간단한 성능 감각

아래와 같은 상황을 생각해봅시다:

for (int i = 0; i < 1000; ++i)
{
    Derived* derived = dynamic_cast<Derived*>(base);
    // Derived 타입 활용 코드..
}

이 코드는 dynamic_cast 자체가 절대적으로 나쁘다는 의미로 보여주는 코드는 아닙니다.

다만 게임 엔진에서는 이런 형태의 반복이 생각보다 쉽게 등장합니다.

예를 들어 수많은 Actor, Component, Object를 매 프레임 순회하면서
“이 객체가 특정 타입인가?”를 계속 확인한다면, 타입 확인 비용은 누적됩니다.

그래서 엔진 코드에서는 dynamic_cast를 무조건 금지한다기보다,
반복적으로 호출되는 핵심 루프에서는 더 예측 가능하고 제어 가능한 타입 시스템을 직접 두는 경우가 많습니다.


🎮 그래서 게임 엔진에서는?

게임 엔진에서는 dynamic_cast 자체가 “나쁜 기능”이라서 피하는 것이 아닙니다.

핵심은 엔진이 타입 시스템을 직접 제어하고 싶어 한다는 점입니다.

대표적으로 언리얼 엔진은 기본 C++ RTTI에 의존하기보다
UClass 기반의 자체 리플렉션 시스템을 사용합니다.

그 이유는 단순히 성능 때문만은 아닙니다.

– 타입 정보를 엔진이 직접 관리하기 위해
– 직렬화, 에디터 노출, GC와 연결하기 위해
– 플랫폼/빌드 설정에 따른 예측 가능성을 높이기 위해
– 런타임 타입 체크를 엔진 구조와 통합하기 위해
– 타입 정보를 저장해두고 필요할 때 객체로 생성하기 위해

즉, 게임 엔진에서 커스텀 RTTI를 구현하는 이유는
dynamic_cast가 무조건 느려서가 아니라,
타입 정보를 엔진의 핵심 시스템으로 확장해야 하기 때문입니다.


💡 핵심 한 줄 정리

dynamic_cast는 안전한 기능이며, 일반적인 코드에서는 충분히 실용적입니다.

다만 게임 엔진처럼 반복 호출이 많은 구조에서는
타입 정보를 더 직접적으로 제어하기 위해 커스텀 RTTI를 설계합니다.

또한, 커스텀 RTTI를 설계해 타입을 직접 관리할 수 있는 구조를 만들면
다양한 기능에서 타입을 활용할 수 있게 됩니다.


그렇다면 이런 질문이 자연스럽게 나옵니다.

❓ dynamic_cast 없이 타입을 확인할 수는 없을까?

다음 글에서는:

👉 커스텀 RTTI를 구현해보기에 앞서 변수의 다운 캐스팅이 왜 위험한지에 대해 포인터와 메모리 관점에서 알아봅니다.

– 업캐스팅 vs 다운 캐스팅
– 다운캐스팅을 잘못했을 때 발생하는 문제점

댓글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

Please turn AdBlock off

Notice for AdBlock users

Please turn AdBlock off