typeid 연산자

typeid 연산자

typeid 연산자는 어떤 타입의 정보를 조회할 때 써요. 다형적(polymorphic) 객체의 동적 타입을 알아야 할 때, 그리고 정적으로 타입을 식별해야 할 때 사용하죠. 상속받은 객체가 실제로는 어떤 파생 타입인지 런타임에 확인하고 싶을 때가 대표적인 상황이에요.

출처: cppreference

본문

문법

typeid( type )        (1)
typeid( expression )  (2)

typeid 표현식은 lvalue 표현식이에요. 다형적 타입 std::type_info의 const 한정 버전이나 그 타입에서 파생된 어떤 타입의 객체를, 정적 저장 기간(static storage duration)으로 가리키는 표현식이죠.

사용 지점에서 표준 라이브러리의 std::type_info 정의가 보이지 않으면 그 프로그램은 ill-formed예요.

설명

  1. type을 나타내는 std::type_info 객체를 가리켜요. type이 참조 타입이면, 그 참조된 타입의 cv-한정이 없는 버전을 나타내는 std::type_info 객체를 가리켜요.
  2. expression을 조사해요.
  • expression이 다형적 타입(가상 함수를 하나라도 선언하거나 상속받는 클래스)의 객체를 식별하는 lvalue(C++11부터는 glvalue) 표현식이라면, typeid는 그 표현식을 평가한 뒤 그 표현식의 동적 타입을 나타내는 std::type_info 객체를 가리켜요.
  • expression이 역참조(indirection) 표현식이고 그 피연산자가 널 포인터 값으로 평가되면, std::bad_typeid 핸들러에 맞는 타입의 예외가 던져져요.
  • 그 외에는 typeid가 표현식을 평가하지 않고, 식별하는 std::type_info 객체가 그 표현식의 정적 타입을 나타내요. lvalue-to-rvalue, 배열-포인터, 함수-포인터 변환은 수행되지 않아요.

이때 type이나 expression의 타입이 클래스 타입이거나 클래스 타입에 대한 참조라면, 그 클래스 타입은 불완전 타입(incomplete type)이면 안 돼요.

| 하지만 prvalue 인자에는 (형식적으로) 임시 객체 생성(temporary materialization)이 수행돼요. 인자는 typeid 표현식이 나타난 문맥에서 파괴 가능해야 해요. | (since C++17) |

type이나 expression의 타입이 cv-한정이면, typeid의 결과는 cv-한정이 없는 타입을 나타내는 std::type_info 객체를 가리켜요(즉 typeid(const T) == typeid(T)).

typeid를 생성·소멸 중인 객체에 쓰면(소멸자나 생성자 안, 생성자 초기화 리스트·기본 멤버 초기화기 포함), 이 typeid가 가리키는 std::type_info 객체는 그 객체가 가장 파생된 클래스가 아니더라도 지금 생성·소멸되고 있는 클래스를 나타내요.

  1. 다른 문맥에서 이런 표현식을 평가하면 결과가 정의되지 않아요(undefined behavior).

참고

typeid를 다형적 타입의 표현식에 적용하면 평가에 런타임 오버헤드(가상 테이블 조회)가 생길 수 있어요. 그 외에는 typeid 표현식이 컴파일 시점에 결정돼요.

typeid가 가리키는 객체의 소멸자가 프로그램 종료 시 실행되는지 여부는 명시되지 않아요.

같은 타입에 대한 모든 typeid 표현식 평가가 같은 std::type_info 객체를 가리킬 거라는 보장은 없어요. 다만 비교가 같다고 나오고, 그 type_info 객체들의 std::type_info::hash_codestd::type_index는 동일해요.

const std::type_info& ti1 = typeid(A);
const std::type_info& ti2 = typeid(A);

assert(&ti1 == &ti2); // not guaranteed
assert(ti1 == ti2); // guaranteed
assert(ti1.hash_code() == ti2.hash_code()); // guaranteed
assert(std::type_index(ti1) == std::type_index(ti2)); // guaranteed
기능 테스트 매크로 표준 기능
__cpp_constexpr 201907L (C++20) 상수 표현식에서의 다형적 typeid

키워드

typeid

예제

아래 예제는 type_info::name이 전체 타입 이름을 돌려주는 구현에서의 출력을 보여줘요. gcc나 비슷한 컴파일러를 쓴다면 c++filt -t로 걸러 보세요.

#include <iostream>
#include <string>
#include <typeinfo>

struct Base {}; // non-polymorphic
struct Derived : Base {};

struct Base2 { virtual void foo() {} }; // polymorphic
struct Derived2 : Base2 {};

int main()
{
    int myint = 50;
    std::string mystr = "string";
    double *mydoubleptr = nullptr;

    std::cout << "myint has type: " << typeid(myint).name() << '\n'
              << "mystr has type: " << typeid(mystr).name() << '\n'
              << "mydoubleptr has type: " << typeid(mydoubleptr).name() << '\n';

    // std::cout << myint is a glvalue expression of polymorphic type; it is evaluated
    const std::type_info& r1 = typeid(std::cout << myint); // side-effect: prints 50
    std::cout << '\n' << "std::cout<<myint has type : " << r1.name() << '\n';

    // std::printf() is not a glvalue expression of polymorphic type; NOT evaluated
    const std::type_info& r2 = typeid(std::printf("%d\n", myint));
    std::cout << "printf(\"%d\\n\",myint) has type : " << r2.name() << '\n';

    // Non-polymorphic lvalue is a static type
    Derived d1;
    Base& b1 = d1;
    std::cout << "reference to non-polymorphic base: " << typeid(b1).name() << '\n';

    Derived2 d2;
    Base2& b2 = d2;
    std::cout << "reference to polymorphic base: " << typeid(b2).name() << '\n';

    try
    {
        // dereferencing a null pointer: okay for a non-polymorphic expression
        std::cout << "mydoubleptr points to " << typeid(*mydoubleptr).name() << '\n'; 
        // dereferencing a null pointer: not okay for a polymorphic lvalue
        Derived2* bad_ptr = nullptr;
        std::cout << "bad_ptr points to... ";
        std::cout << typeid(*bad_ptr).name() << '\n';
    }
    catch (const std::bad_typeid& e)
    {
        std::cout << " caught " << e.what() << '\n';
    }
}

가능한 출력:

======== output from Clang ========
myint has type: i
mystr has type: NSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE
mydoubleptr has type: Pd
50
std::cout<<myint has type : NSt3__113basic_ostreamIcNS_11char_traitsIcEEEE
printf("%d\n",myint) has type : i
reference to non-polymorphic base: 4Base
reference to polymorphic base: 8Derived2
mydoubleptr points to d
bad_ptr points to...  caught std::bad_typeid

======== output from MSVC ========
myint has type: int
mystr has type: class std::basic_string<char,struct std::char_traits<char>,
class std::allocator<char> >
mydoubleptr has type: double * __ptr64
50
std::cout<<myint has type : class std::basic_ostream<char,struct std::char_traits<char> >
printf("%d\n",myint) has type : int
reference to non-polymorphic base: struct Base
reference to polymorphic base: struct Derived2
mydoubleptr points to double
bad_ptr points to...  caught Attempted a typeid of nullptr pointer!

더 알아보기

  • std::type_infotypeid 연산자가 돌려주는 클래스로, 어떤 타입의 정보를 담고 있어요. 타입 정보 라이브러리 문서에서 자세히 볼 수 있어요.
  • std::type_indexstd::type_info 객체를 감싸서 연관·비정렬 연관 컨테이너의 인덱스로 쓸 수 있게 해 주는 래퍼예요.
  • cppreference의 typeid 원문에서 결함 보고(defect report) 기록을 더 볼 수 있어요.