콘텐츠로 이동

런타임 타입 정보 (RTTI)

런타임 타입 정보(RTTI) 사용을 피하세요.

정의:

RTTI는 프로그래머가 런타임에 객체의 C++ 클래스를 알아낼 수 있게 해 줍니다. typeid나 dynamic_cast를 사용해서 합니다.

장점:

RTTI의 표준적인 대안들은(아래에서 설명합니다) 문제가 되는 클래스 계층을 수정하거나 다시 설계해야 합니다. 그런 수정이 불가능하거나 바람직하지 않을 때도 있으며, 널리 쓰이거나 성숙한 코드에서는 특히 그렇습니다.

RTTI는 일부 단위 테스트에서 유용할 수 있습니다. 예를 들어 새로 만든 객체가 기대한 동적 타입을 갖는지 검증해야 하는 팩토리 클래스 테스트에서 유용합니다. 객체와 그 목(mock) 사이의 관계를 관리하는 데에도 유용합니다.

RTTI는 여러 추상 객체를 함께 다룰 때 유용합니다. 다음을 생각해 보세요.

bool Base::Equal(Base* other) = 0;
bool Derived::Equal(Base* other) {
  Derived* that = dynamic_cast<Derived*>(other);
  if (that == nullptr)
    return false;
  ...
}

단점:

런타임에 객체의 타입을 물어봐야 한다는 것은 설계 문제인 경우가 많습니다. 런타임에 객체의 타입을 알아야 한다는 것은 클래스 계층 설계에 결함이 있다는 신호인 경우가 많습니다.

RTTI를 무절제하게 사용하면 코드를 유지보수하기 어려워집니다. 타입에 기반한 결정 트리나 switch 문이 코드 전체에 흩어지게 되고, 이후 변경할 때마다 그것들을 모두 살펴봐야 합니다.

결정:

RTTI에는 정당한 쓰임이 있지만 남용되기 쉬우므로 사용할 때 주의해야 합니다. 단위 테스트에서는 자유롭게 써도 되지만, 다른 코드에서는 가능하면 피하세요. 특히 새 코드에서 RTTI를 쓰기 전에는 다시 한번 생각하세요. 객체의 클래스에 따라 다르게 동작하는 코드를 작성해야 한다면, 타입을 물어보는 대신 아래 대안 중 하나를 고려하세요.

  • 특정 하위 클래스 타입에 따라 서로 다른 코드 경로를 실행하는 데는 가상 메서드가 선호되는 방법입니다. 이렇게 하면 그 일이 객체 자신 안에 놓입니다.
  • 그 일이 객체 바깥, 즉 어떤 처리 코드에 속한다면 방문자(Visitor) 디자인 패턴 같은 이중 디스패치 해법을 고려하세요. 그러면 객체 바깥의 기능이 언어에 내장된 타입 시스템을 사용해 클래스의 타입을 판별할 수 있습니다.

프로그램의 논리가 어떤 기반 클래스 인스턴스가 실제로는 특정 파생 클래스의 인스턴스임을 보장한다면, 그 객체에는 dynamic_cast를 자유롭게 써도 됩니다. 보통은 absl::down_cast가 그보다도 더 나은 대안을 제공합니다.

타입에 기반한 결정 트리는 코드가 잘못된 방향으로 가고 있다는 강한 신호입니다.

if (typeid(*data) == typeid(D1)) {
  ...
} else if (typeid(*data) == typeid(D2)) {
  ...
} else if (typeid(*data) == typeid(D3)) {
...

이런 코드는 클래스 계층에 하위 클래스가 추가되면 대개 깨집니다. 게다가 하위 클래스의 성질이 바뀌면 영향받는 코드 조각을 전부 찾아 고치기가 어렵습니다.

RTTI와 비슷한 우회책을 직접 만들지 마세요. RTTI를 반대하는 논거는 타입 태그를 단 클래스 계층 같은 우회책에도 똑같이 적용됩니다. 게다가 그런 우회책은 진짜 의도를 가려 버립니다.


옮긴이 풀이

핵심: RTTI를 쓰지 말 것

RTTI는 typeid나 dynamic_cast로 런타임에 객체의 실제 클래스를 알아내는 기능입니다. 런타임에 타입을 물어봐야 한다는 것은 대개 클래스 계층 설계에 결함이 있다는 신호입니다.

대안

객체의 클래스에 따라 다르게 동작해야 한다면, 타입을 묻지 말고:

  1. 가상 메서드: 동작을 객체 자신에게 넣어, 하위 클래스마다 다른 경로를 타게 합니다. (가장 선호)
  2. 이중 디스패치(방문자 패턴): 동작이 객체 외부의 처리 코드에 속할 때.
// 나쁨: 타입 기반 결정 트리 — 하위 클래스가 늘면 깨짐
if (typeid(*data) == typeid(D1)) { ... }
else if (typeid(*data) == typeid(D2)) { ... }

허용되는 경우

  • 단위 테스트: 팩토리가 만든 객체의 동적 타입 확인 등에는 자유롭게 사용.
  • 프로그램 로직이 특정 파생 타입임을 보장할 때의 다운캐스트: 보통 dynamic_cast보다 absl::down_cast가 더 낫습니다.

직접 구현도 금지

타입 태그를 단 클래스 계층 같은 "수제 RTTI"도 만들지 마세요. RTTI를 막는 이유가 그대로 적용되며, 의도만 가려집니다.