반응형

RTTI

 

Run-Time Type Information

Run-Time Type Information In this article --> Run-time type information (RTTI) is a mechanism that allows the type of an object to be determined during program execution. RTTI was added to the C++ language because many vendors of class libraries were imple

docs.microsoft.com

더보기

RTTI(런타임 형식 정보)는 프로그램 실행 중에 개체의 형식이 결정될 수 있도록 하는 메커니즘입니다. 많은 클래스 라이브러리 공급업체가 이 기능을 자체적으로 구현하고 있었기 때문에 RTTI가 C++ 언어에 추가되었습니다. 이 때문에 라이브러리 간에 호환되지 않는 문제가 발생하게 되었으므로 언어 수준에서 런타임 형식 정보에 대한 지원이 필요하다는 사실이 명백해졌습니다.

명확성을 위해 여기에서 RTTI에 대한 설명은 거의 전적으로 포인터에 국한됩니다. 하지만 설명된 개념은 참조에도 적용됩니다.

런타임 형식 정보에는 다음 세 가지 기본 C++ 언어 요소가 있습니다.

  • dynamic_cast 연산자

    다형 형식을 변환하는 데 사용됩니다.

  • typeid 연산자

    개체의 정확한 형식을 식별하는 데 사용됩니다.

  • type_info 클래스

    typeid 연산자에서 반환된 형식 정보를 저장하는 데 사용됩니다.

참고 항목

캐스팅

클래스, 자료형에 관한 정보는 type_info 클래스에 보관되어 있는데 이를 드러내는 C++의 방식이 RTTI라 보면 되겠다. 물론 이와같은 메커니즘은 다른 언어에서도 존재한다.

 

 

 

type_info + typeid

 

type_info 클래스

type_info 클래스type_info Class 이 문서의 내용 --> 합니다 type_info 형식 정보를 컴파일러에 의해 프로그램 내에서 생성 하는 클래스에 설명 합니다.The type_info class describes type information generated within the program by the compiler. 이 클래스의 개체는 형식의 이름에 대한 포인터를 효과적으로 저장합니다.Objects of this class eff

docs.microsoft.com

더보기

The type_info class describes type information generated within the program by the compiler. Objects of this class effectively store a pointer to a name for the type. The type_info class also stores an encoded value suitable for comparing two types for equality or collating order. The encoding rules and collating sequence for types are unspecified and may differ between programs.

The <typeinfo> header file must be included in order to use the type_info class. The interface for the type_info class is:

1
2
3
4
5
6
7
8
9
10
class type_info {
public:
    virtual ~type_info();
    size_t hash_code() const
    _CRTIMP_PURE bool operator==(const type_info& rhs) const;
    _CRTIMP_PURE bool operator!=(const type_info& rhs) const;
    _CRTIMP_PURE int before(const type_info& rhs) const;
    _CRTIMP_PURE const char* name() const;
    _CRTIMP_PURE const char* raw_name() const;
};
 
 

You cannot instantiate objects of the type_info class directly, because the class has only a private copy constructor. The only way to construct a (temporary) type_info object is to use the typeid operator. Since the assignment operator is also private, you cannot copy or assign objects of class type_info.

type_info::hash_code defines a hash function suitable for mapping values of type typeinfo to a distribution of index values.

The operators == and != can be used to compare for equality and inequality with other type_info objects, respectively.

There is no link between the collating order of types and inheritance relationships. Use the type_info::before member function to determine the collating sequence of types. There is no guarantee that type_info::before will yield the same result in different programs or even different runs of the same program. In this manner, type_info::before is similar to the address-of (&) operator.

The type_info::name member function returns a const char* to a null-terminated string representing the human-readable name of the type. The memory pointed to is cached and should never be directly deallocated.

The type_info::raw_name member function returns a const char* to a null-terminated string representing the decorated name of the object type. The name is actually stored in its decorated form to save space.Consequently, this function is faster than type_info::name because it doesn't need to undecorate the name.The string returned by the type_info::raw_name function is useful in comparison operations but is not readable. If you need a human-readable string, use the type_info::name function instead.

Type information is generated for polymorphic classes only if the /GR (Enable Run-Time Type Information)compiler option is specified.

See also

Run-Time Type Information

 

typeid 연산자

typeid 연산자typeid Operator 이 문서의 내용 --> 구문Syntax typeid(type-id) typeid(expression) Typeid 연산자를 사용 하면 런타임에 개체의 형식을 확인할 수 있습니다.The typeid operator allows the type of an object to be determined at run time. Typeid 의 결과는 const type_info&입니다.The result of typei

docs.microsoft.com

더보기

Syntax

1
2
typeid(type-id) 
typeid(expression)
 

Remarks

The typeid operator allows the type of an object to be determined at run time.

The result of typeid is a const type_info&. The value is a reference to a type_info object that represents either the type-id or the type of the expression, depending on which form of typeid is used. See type_info Class for more information.

The typeid operator does not work with managed types (abstract declarators or instances), see typeid for information on getting the Type of a specified type.

The typeid operator does a run-time check when applied to an l-value of a polymorphic class type, where the true type of the object cannot be determined by the static information provided. Such cases are:

  • A reference to a classdsd

  • A pointer, dereferenced with *

  • A subscripted pointer (i.e. [ ]). (Note that it is generally not safe to use a subscript with a pointer to a polymorphic type.)

If the expression points to a base class type, yet the object is actually of a type derived from that base class, a type_info reference for the derived class is the result. The expression must point to a polymorphic type (a class with virtual functions). Otherwise, the result is the type_info for the static class referred to in the expression. Further, the pointer must be dereferenced so that the object it points to is used. Without dereferencing the pointer, the result will be the type_info for the pointer, not what it points to. For example:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// compile with: /GR /EHsc
#include <iostream>
#include <typeinfo.h>
 
class Base {
public:
   virtual void vvfunc() {}
};
 
class Derived : public Base {};
 
using namespace std;
int main() {
   Derived* pd = new Derived;
   Base* pb = pd;
   cout << typeid( pb ).name() << endl;   //prints "class Base *"
   cout << typeid*pb ).name() << endl;   //prints "class Derived"
   cout << typeid( pd ).name() << endl;   //prints "class Derived *"
   cout << typeid*pd ).name() << endl;   //prints "class Derived"
   delete pd;
}
 
 

 

If the expression is dereferencing a pointer, and that pointer's value is zero, typeid throws a bad_typeid exception. If the pointer does not point to a valid object, a __non_rtti_object exception is thrown, indicating an attempt to analyze the RTTI that triggered a fault (like access violation), because the object is somehow invalid (bad pointer or the code wasn't compiled with /GR).

If the expression is neither a pointer nor a reference to a base class of the object, the result is a type_inforeference representing the static type of the expression. The static type of an expression refers to the type of an expression as it is known at compile time. Execution semantics are ignored when evaluating the static type of an expression. Furthermore, references are ignored when possible when determining the static type of an expression:

 

1
2
3
4
5
6
7
#include <typeinfo>
 
int main()
{
   typeid(int== typeid(int&); // evaluates to true
}
 
 

typeid can also be used in templates to determine the type of a template parameter:

1
2
3
4
5
6
7
8
// compile with: /c
#include <typeinfo>
template < typename T >
T max( T arg1, T arg2 ) {
   cout << typeid( T ).name() << "s compared." << endl;
   return ( arg1 > arg2 ? arg1 : arg2 );
}
 
 

 

See also

Run-Time Type Information
Keywords

type_info 클래스는 접은 글에서 참고를 할 뿐 아니라 실제 그 선언클래스를 참고하면, 아예 생성을 할 수 없게끔 꾸며 놓았다. 다만, 우리가 외부에서 다룰 수 있는 몇 개의 operator와 함수들을 공개할 뿐이다.

 

type_info 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#pragma warning(push)
#pragma warning(disable: 4577// 'noexcept' used with no exception handling mode specified
class type_info
{
public:
 
    type_info(const type_info&= delete;
    type_info& operator=(const type_info&= delete;
 
    size_t hash_code() const noexcept
    {
        return __std_type_info_hash(&_Data);
    }
 
    bool operator==(const type_info& _Other) const noexcept
    {
        return __std_type_info_compare(&_Data, &_Other._Data) == 0;
    }
 
    bool operator!=(const type_info& _Other) const noexcept
    {
        return __std_type_info_compare(&_Data, &_Other._Data) != 0;
    }
 
    bool before(const type_info& _Other) const noexcept
    {
        return __std_type_info_compare(&_Data, &_Other._Data) < 0;
    }
 
    const char* name() const noexcept
    {
        #ifdef _M_CEE_PURE
        return __std_type_info_name(&_Data, static_cast<__type_info_node*>(__type_info_root_node.ToPointer()));
        #else
        return __std_type_info_name(&_Data, &__type_info_root_node);
        #endif
    }
 
    const char* raw_name() const noexcept
    {
        return _Data._DecoratedName;
    }
 
    virtual ~type_info() noexcept;
 
private:
 
    mutable __std_type_info_data _Data;
};
#pragma warning(pop)
 
 

7, 8번 줄을 확인하면 복사생성자와 대입연산자가 아예 [= delete(C++11)]로 하여금 아예 구현하지 않는 것으로 정의된다. 44번줄 가상 소멸자로 하여금 직접 메모리를 해제할 수도 없다. (noexept = 컴파일러에게 아예 오류가 발생하지 않는 부분이므로 빠르게 지나가라는 소리(C++11)) 

 

접은글 설명에서는 해당 클래스가 별도의 생성과 소멸이 불가능할 뿐더러 정보를 저장하는 것이 아니라 정보를 드러낸다고 표현하였다.(물론 저장하는 구조체는 private로 숨겨져있어 엄연하게 따지면 존재하긴 한다.) type_info 클래스는 바로 typeid 연산자에서 const type_info& 형으로 반환해주면서 본격적으로 쓰이기 시작한다.

 

 

typeid

 

기본적인 폼은

typeid(메모리할당된 객체나 변수 이름)

typeid(함수명)

 

 

 

typeid 기본 쓰임_

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <typeinfo.h>
 
class Base {
public:
    virtual void vvfunc() {}
};
 
class Derived : public Base {};
 
using namespace std;
int main() {
 
 
    Derived* pd = new Derived;
    Base* pb = pd;
    cout << typeid(pb).name() << endl;   //prints "class Base *"
    cout << typeid(*pb).name() << endl;   //prints "class Derived"
    cout << typeid(pd).name() << endl;   //prints "class Derived *"
    cout << typeid(*pd).name() << endl;   //prints "class Derived"
}
 
 

15, 16줄을 확인하면 포인터를 선언하였고, Derived 클래스를 힙에 할당하여 참조하도록 하였다. 이를 18, 20줄에서 확인하게되면 다시 (*)역참조 연산으로 값을 가져오게 한다. 하지만 여기서 역참조시 Dervie를 Base로 받을 뿐 실제 값은 Derive를 받기 때문에 그 클래스의 이름은 class Derived가 된다.

 

 

 

 

typeid 여러가지 연산_

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <iostream>
#include <typeinfo.h>
 
class Base {
public:
    virtual void vvfunc() {}
};
 
class Derived : public Base {};
 
 
class Other {};
void func1() {};
 
 
 
using namespace std;
int main() {
 
 
    Derived* pd = new Derived;
    Base* pb = pd;
    Other* po = new Other;
 
 
    auto a = []()->int { cout << "lamda func A" << endlreturn 0; };
    auto b = []()->int { cout << "lamda func B" << endlreturn 0; };
 
 
    cout << typeid(func1).name() << endl;
    cout << typeid(cout).raw_name() << endl;
    cout << typeid(&Base::vvfunc).name() << endl;
    cout << typeid(a).name() << endl;
    cout << endl;
 
 
    cout << "pb vs pd: " << typeid(pb).before(typeid(pd)) << endl;  // pb vs pd
    cout << "pd vs pb: " << typeid(pd).before(typeid(pb)) << endl;  // pb vs pd
    cout << "pd vs po: " << typeid(pd).before(typeid(po)) << endl;  // pb vs po
    cout << "pb vs po: " << typeid(pb).before(typeid(po)) << endl;  // pd vs po
    cout << "po vs pb: " << typeid(po).before(typeid(pb)) << endl;  // pb vs po
    cout << "po vs pd: "<< typeid(po).before(typeid(pd)) << endl;  // pd vs po
 
 
    cout << endl;
 
    cout << "Base vs Derived: " << typeid(Base).before(typeid(Derived)) << endl;   //  Base vs Derived
    cout << "Derived vs Base: " << typeid(Derived).before(typeid(Base)) << endl;   //  Base vs Derived
    cout << "Base vs Other: " << typeid(Base).before(typeid(Other)) << endl;        //  Base vs Other
    cout << "Other vs Derived: " << typeid(Other).before(typeid(Derived)) << endl;  //  Derived vs Other
    cout << "Lamda A vs Lamda B: " << typeid(a).before(typeid(b)) << endl;   //  Lamda A vs Lamda B
    cout << "Lamda A vs pointer pd: " << typeid(a).before(typeid(pd)) << endl;  //  Lamda A vs pointer
    cout << "pointer pd vs Lamda A: " << typeid(pd).before(typeid(a)) << endl;  //  Lamda A vs pointer
 
    cout << endl;
 
 
 
    // cout << typeid(func1).before(typeid(main)) << endl;
    // cout << typeid(main).before(typeid(func1)) << endl;
}
 
 

 

 

typeid 여러가지 연산 실행 결과_

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void __cdecl(void)
.?AV?$basic_ostream@DU?$char_traits@D@std@@@std@@
void (__thiscall Base::*)(void)
class <lambda_cc0397e8003d8d6e5c3822c780159f83>
 
pb vs pd: 1
pd vs pb: 0
pd vs po: 1
pb vs po: 1
po vs pb: 0
po vs pd: 0
 
Base vs Derived: 1
Derived vs Base: 0
Base vs Other: 1
Other vs Derived: 0
Lamda A vs Lamda B: 0
Lamda A vs pointer pd: 1
pointer pd vs Lamda A: 0
 

raw_name()_

실제 클래스 내부든 간에 연산에 필요한 이름을 그대로 반환한다. -> 가공 연산이 없다.

name()은 우리가 읽을 수 있도록 char* 형으로 가공한 것이다.

 

 

before()_

피연산되는 변수이름이나 클래스, 함수 따위들이 먼저 선언되었는지에 대한 여부를 따진다. 만약 먼저 선언되었다면 0, 그렇지 않다면 1을 반환한다.

 

즉,

typeid(A).before(typeid(B)) -> A가 B보다 먼저 선언되어있느냐?

 

결과적으로 보면 함수뿐 아니라 람다함수까지 연산이 되는 모습이며, 단 람다함수를 제외한 전역함수 및 클래스 내부 함수들은 before 연산을 진행할 수 없었다.

 

 

 

bad_typeid

예외처리 -> try~catch문에서 에러를 잡아낼 때 그 [예외]를 throw 시켜주는 녀석이다. 여기서 typeid가 예외로 처리될 경우는 바로 해당 변수가 NULL 포인터일 때이다. 이것은 예제를 보면 바로 이해가 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// compile with: /EHsc /GR
#include <typeinfo.h>
#include <iostream>
 
class A{
public:
   // object for class needs vtable
   // for RTTI
   virtual ~A();
};
 
using namespace std;
int main() {
A* a = NULL;
 
try {
   cout << typeid(*a).name() << endl;  // Error condition
   }
catch (bad_typeid){
   cout << "Object is NULL" << endl;
   }
}
 
 

20 줄을 보면 예외를 받는 자리에 바로 [bad_typeid] 클래스가 들어가 있다. 해당 클래스는 NULL 포인터로 typeid를 산출할 시 예외를 던져준다.

 

 

 

캐스팅 + 클래스의 다형성

 

캐스팅

캐스팅Casting 이 문서의 내용 --> C++ 언어에서는 클래스가 가상 함수를 포함하는 기본 클래스에서 파생되는 경우 해당 기본 클래스 형식에 대한 포인터를 사용하여 파생 클래스 개체에 있는 가상 함수의 구현을 호출할 수 있습니다.The C++ language provides that if a class is derived from a base class containing virtual functions, a pointer to that base c

docs.microsoft.com

 

 

C++ 언어에서는 클래스가 가상 함수를 포함하는 기본 클래스에서 파생되는 경우 해당 기본 클래스 형식에 대한 포인터를 사용하여 파생 클래스 개체에 있는 가상 함수의 구현을 호출할 수 있습니다. 가상 함수를 포함하는 클래스를 "다형 클래스"라고도 합니다.

파생 클래스에는 해당 클래스가 파생되는 모든 기본 클래스의 정의가 완전히 포함되어 있으므로 포인터를 이러한 기본 클래스에 대한 클래스 계층 구조 위로 캐스팅해도 됩니다. 기본 클래스에 포인터를 지정하면 포인터를 계층 구조 아래로 캐스팅해도 안전할 수 있습니다. 포인터가 지정 중인 개체가 실제로 기본 클래스에서 파생된 형식이면 안전합니다. 이 경우 실제 개체는 "완전한 개체"라고 합니다. 기본 클래스의 포인터는 완전한 개체의 "하위 개체"를 가리킨다고 합니다. 예를 들어 다음 그림과 같은 클래스 계층 구조를 참고해 보겠습니다.

 
클래스 계층 구조

C 형식의 개체는 다음 그림과 같이 시각화될 수 있습니다.

 
하위 개체 B 및 A 포함 클래스 C

C 클래스의 인스턴스를 제공하면 B 하위 개체 및 A 하위 개체가 있습니다. C  A 하위 개체를 포함한 B의 인스턴스는 "완전한 개체"입니다.

런타임 형식 정보를 사용하면 실제로 포인터가 완전한 개체를 가리키며 해당 계층 구조에서 다른 개체를 가리키도록 안전하게 캐스팅될 수 있는지 여부를 확인할 수 있습니다. dynamic_cast 연산자는 이러한 캐스팅 형식을 만드는 데 사용할 수 있습니다. 이러한 연산자는 안전하게 작업하는 데 필요한 런타임 검사도 수행합니다.

비 다형 형식을 변환에 사용할 수 있습니다 합니다 static_cast 연산자 (이 항목에서는 각 사용 하기에 적합 하는 경우 및 정적 및 동적 캐스팅 변환 간의 차이 설명 하는 데 사용).

이 단원에서는 다음 항목에 대해 설명합니다.

참고자료

식(C++)

 

 

dynamic_cast

이 구문은 한 번 아예 접은글 쪽에서 원문 번역을 해보았다. 그래서 따로 설명은 없을 것 같으니... 좀더 자세한 것을 보고 싶다면 사이트로 들어가서 원문을 보기 바란다.

 

 

dynamic_cast 연산자

dynamic_cast 연산자dynamic_cast Operator 이 문서의 내용 --> 피연산자를 변환 expression 형식의 개체에 type-id입니다.Converts the operand expression to an object of type type-id. 구문Syntax dynamic_cast < type-id > ( expression ) type-id 대 한 포인터 또는 이전에 정의 된 클래스 형식에 대 한 참조는 "void"포인터 여

docs.microsoft.com

더보기

구문

dynamic_cast < type-id > ( expression )

설명

type-id은 포인터 또는 이전에 정의된 클래스 형식에 대한 참조형 혹은 "void포인터"여야 합니다. type-id이 포인터인 경우 [expression] 형식은 포인터여야 하며 type-id이 참조인 경우에는 l-value 값이어야 합니다.

static_cast 해당 링크는 static과 dynamic간 캐스팅 변환에 대한 차이점을 설명하며, 사용시에 적합한 설명을 합니다.

 

관리 코드에서 dynamic_cast 이 실행 시 두 가지 주요 실패요인이 있습니다:

  • dynamic_cast boxed enum의 내부 형식에 대한 포인터로 변환된 런타임 시 실패합니다, 대신 변환된 포인터로 0을 반환합니다.

  • dynamic_cast 런타임 시 오류가 발생할 때에, 값을 가리키는 내부 포인터의 type-id는 예외처리 과정에서 더이상 throw를 하지 않습니다. 이제 cast 시에 throw 하는 대신 0 포인터 값이 반환됩니다.

?? boxed enum(박스 열거형) + interior pointer(내부 포인터)

 

 

만약 type-id가 명확히 액세스가 가능하고, 직접 또는 간접적으로 [expression] 값에 대해 기본클래스[base class] 포인터 형이라면, 해당 type-id 형에서 고유한 하위 개체(subobject -> 상위 클래스의 인스턴스)의 포인터가 산출됩니다. 

 

예를 들어:

1
2
3
4
5
6
7
8
9
10
11
12
// compile with: /c
class B { };
class C : public B { };
class D : public C { };
 
void f(D* pd) {
   C* pc = dynamic_cast<C*>(pd);   // ok: C is a direct base class
                                   // pc points to C subobject of pd
   B* pb = dynamic_cast<B*>(pd);   // ok: B is an indirect base class
                                   // pb points to B subobject of pd
}
 
 

 

파생된 클래스에서 클래스 계층에 대한 포인터가 위쪽 클래스로 이동하기 때문에 이러한 유형의 변환을 "업캐스팅" 라고 합니다. 업캐스트는 암시적 변환이 수행됩니다.

 

만약 type-id이 void * 형이라면, [인자] 형의 실제 형식을 결정하는 런타임 검사가 수행됩니다. 결과는 [expression]가 가리키는 대상에 완전한 개체에 대한 포인터입니다. 

 

예를 들어:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// compile with: /c /GR
class A {virtual void f();};
class B {virtual void f();};
 
void f() {
   A* pa = new A;
   B* pb = new B;
   void* pv = dynamic_cast<void*>(pa);
   // pv now points to an object of type A
 
   pv = dynamic_cast<void*>(pb);
   // pv now points to an object of type B
}
 
 

 

type-id가 void* 형이 아닌 경우, 런타임 검사를(runtime-check) 시행해 [expression] 값으로 가리키는 개체가 type-id 형으로 변환될 수 있는지 확인 후 변환을 수행합니다.

 

만약 [expression] 형이 type-id 형식의 기본 클래스인 경우, 런타임 검사를(runtime-check) 시행해 [expression] 값이 가리키는 개체가 type-id 형으로 완전히 변환될 수 있는지 확인 후 변환을 수행합니다. 가능하다면 결과는 type-id 형의 완전한 개체 형식 포인터를 산출합니다. (이는 기본 클래스를 넣어도 기본 클래스로 산출되는 것이 아닌 자식 클래스로 변환된다는 소리다.)

 

예를 들어:

1
2
3
4
5
6
7
8
9
10
11
12
// compile with: /c /GR
class B {virtual void f();};
class D : public B {virtual void f();};
 
void f() {
   B* pb = new D;   // unclear but ok
   B* pb2 = new B;
 
   D* pd = dynamic_cast<D*>(pb);   // ok: pb actually points to a D
   D* pd2 = dynamic_cast<D*>(pb2);   // pb2 points to a B not a D
}
 
 

 

클래스 계층에 대한 포인터가 아래쪽 클래스로 이동하기 때문에 이러한 유형의 변환을 "다운캐스팅" 라고 합니다. 업캐스트는 암시적 변환이 수행됩니다. 암시적 수행이 아니다.

 

다음 샘플에서는 dynamic_cast을 사용하여 클래스가 어떤 특정 인스턴스인지 판별합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// compile with: /clr
using namespace System;
 
void PrintObjectType( Object^o ) {
   ifdynamic_cast<String^>(o) )
      Console::WriteLine("Object is a String");
   else ifdynamic_cast<int^>(o) )
      Console::WriteLine("Object is an int");
}
 
int main() {
   Object^o1 = "hello";
   Object^o2 = 10;
 
   PrintObjectType(o1);
   PrintObjectType(o2);
}
 
 

 

다중 상속의 경우에서 모호성에 대한 가능성이 생겨납니다. 다음 그림에 표시된 클래스 계층 구조를 참고합니다.

CLR 형식의 경우 dynamic_cast 변환이 암시적으로 수행할 수 있으면 no-op이 산출되며, 혹은 MSIL로 인해 isinst 동적 검사를 수행하고 변환이 실패하면 nullptr을 반환합니다.

 

여러 상속을 보여주는 클래스 계층 구조

 

D 형식의 개체를 가리키는 포인터는 안전하게 B나 C로 캐스팅할 수 있습니다(업캐스팅). 그러나 D가 A의 개체 포인터로 캐스팅된다고 할때, 어떤 인스턴스로 변환되어야 하는가? 이렇게 모호한 캐스팅 오류가 발생합니다. 이 문제를 해결하기 위해 두 개의 모호하지 않은 캐스트를 수행할 수 있습니다.

 

예를 들어:

1
2
3
4
5
6
7
8
9
10
11
// compile with: /c /GR
class A {virtual void f();};
class B {virtual void f();};
class D : public B {virtual void f();};
 
void f() {
   D* pd = new D;
   B* pb = dynamic_cast<B*>(pd);   // first cast to B
   A* pa2 = dynamic_cast<A*>(pb);   // ok: unambiguous
}
 
 

 

(pb에서 A로 업캐스팅 하는 모습이다. 이럴 경우 위에서 말한 업캐스팅의 모호성이 사라진다.)

 

 

가상 기본 클래스를 사용 하는 경우에 추가 모호성을 유추할 수 있습니다. 다음 그림에 표시된 클래스 계층 구조를 참고합니다.

 

 

가상 기본 클래스를 보여 주는 클래스 계층 구조

 

이 계층에서 A 는 가상 기본 클래스입니다. 클래스 E의 인스턴스가 클래스 A의 하위객체(subobject)를 포인터로 할당된다고 했을 때, 포인터 B와 dynamic_cast 연산 시 모호성으로 인해 캐스팅을 실패합니다. 먼저 완전히 다시 E 개체로 캐스팅 한 후, 올바르게 B 개체에 도달하기 위해 명확한 방식으로 계층구조를 거슬러 올라가야합니다.

 

 

 

 

다음 그림에 표시 된 클래스 계층 구조를 참고합니다.

중복된 기본 클래스를 보여 주는 클래스 계층 구조

 

개체 하위개체 E 형에 D를 포인터로 부여하고, 하위개체 D에서 왼쪽 제일끝 A로 가기까지 3가지의 캐스팅변환을 실시할 수 있습니다. D포인터에서 E포인터로 가는 dynamic_cast를 진행하고(dynamic_cast 혹은 암시적 변환에 의해), E에서 B로 가는 캐스팅을 실시합니다. 마지막으로 B에서 A로가는 암시적 변환을 진행합니다.

 

예를 들어:

1
2
3
4
5
6
7
8
9
10
11
12
13
// compile with: /c /GR
class A {virtual void f();};
class B : public A {virtual void f();};
class C : public A { };
class D {virtual void f();};
class E : public B, public C, public D {virtual void f();};
 
void f(D* pd) {
   E* pe = dynamic_cast<E*>(pd);
   B* pb = pe;   // upcast, implicit conversion
   A* pa = pb;   // upcast, implicit conversion
}
 
 

 

dynamic_cast 연산자를 사용하여 "크로스 캐스트"를 수행 할 수도 있습니다. 동일한 클래스 계층 구조를 사용하면 완성된 객체의 유형이 E인 경우 B 하위객체에서 D 하위객체로 포인터를 캐스팅 할 수 있습니다.

 

크로스 캐스트를 고려할 때, 포인터에서 D 로의 변환을 단지 두 단계로 가장 왼쪽의 A 하위 오브젝트에 대한 포인터로 실제로 변환하는 것이 가능합니다. D에서 B로 크로스 캐스트 한 다음 B에서 A로 암시적으로 변환 할 수 있습니다.

 

예를 들어:

1
2
3
4
5
6
7
8
9
10
11
12
// compile with: /c /GR
class A {virtual void f();};
class B : public A {virtual void f();};
class C : public A { };
class D {virtual void f();};
class E : public B, public C, public D {virtual void f();};
 
void f(D* pd) {
   B* pb = dynamic_cast<B*>(pd);   // cross cast
   A* pa = pb;   // upcast, implicit conversion
}
 
 

 

null 포인터는 dynamic_cast 연산으로 도달 형식의 null 포인터 값으로 변환됩니다.

 

dynamic_cast < type-id > ( expression ) 형식을 사용할 경우, ( expression )가 type-id 형으로 안전하게 변환될 수 없으면, 런타임 검사는 캐스트를 실패합니다.

 

 

1
2
3
4
5
6
7
8
9
10
// compile with: /c /GR
class A {virtual void f();};
class B {virtual void f();};
 
void f() {
   A* pa = new A;
   B* pb = dynamic_cast<B*>(pa);   // fails at runtime, not safe;
   // B not derived from A
}
 
 

 

포인터 형식으로 캐스팅이 실패한 값은 null 포인터가 됩니다. 실패한 캐스팅 형식을 throw하여 예외처리를 bad_cast로 캐스팅 합니다. 만약 ( expression )가 어떤 포인터를 가리키지 않거나 개체 참조 형이면, 는 __non_rtti_object 예외가 throw 됩니다.

 

__non_rtti_object에 대한 설명은  typeid 해당 링크를 참고하기 바란다..

예제

다음 샘플에는 기본 클래스 (구조체는) 포인터를 (C 구조체) 개체를 만듭니다. 가상 함수가 있다는 사실과 함께 런타임 다형성이 가능합니다.

또한 샘플 계층의 비가상 함수를 호출합니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// compile with: /GR /EHsc
#include <stdio.h>
#include <iostream>
 
struct A {
    virtual void test() {
        printf_s("in A\n");
   }
};
 
struct B : A {
    virtual void test() {
        printf_s("in B\n");
    }
 
    void test2() {
        printf_s("test2 in B\n");
    }
};
 
struct C : B {
    virtual void test() {
        printf_s("in C\n");
    }
 
    void test2() {
        printf_s("test2 in C\n");
    }
};
 
void Globaltest(A& a) {
    try {
        C &= dynamic_cast<C&>(a);
        printf_s("in GlobalTest\n");
    }
    catch(std::bad_cast) {
        printf_s("Can't cast to C\n");
    }
}
 
int main() {
    A *pa = new C;
    A *pa2 = new B;
 
    pa->test();
 
    B * pb = dynamic_cast<*>(pa);
    if (pb)
        pb->test2();
 
    C * pc = dynamic_cast<*>(pa2);
    if (pc)
        pc->test2();
 
    C ConStack;
    Globaltest(ConStack);
 
   // will fail because B knows nothing about C
    B BonStack;
    Globaltest(BonStack);
}
 
 

 

Output

1
2
3
4
in C
test2 in B
in GlobalTest
Can't cast to C
 

참고자료

캐스팅 연산자   

키워드(C++)

dynamic_cast는 통번역을 하였기 때문에 접은글을 모조리 읽으면 된다. 따로 정리는 안 할 것이며(그림설명까지 접은글에 있다.) 앞으로는 역시 기존 방식대로 영문을 정리하는 식으로 써야겠다.

bad_cast 예외

bad_cast 예외는 참조 형식에 대한 실패한 캐스팅 결과로 dynamic_cast 연산자가 throw합니다.

구문

catch (bad_cast)

      statement

설명

bad_cast의 인터페이스는 다음과 같습니다.

1
2
3
4
5
6
class bad_cast : public exception {
public:
   bad_cast(const char * _Message = "bad cast");
   bad_cast(const bad_cast &);
   virtual ~bad_cast();
};
 
 

다음 코드에는 bad_cast 예외를 throw하는 실패한 dynamic_cast의 예제가 포함되어 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// compile with: /EHsc /GR
#include <typeinfo.h>
#include <iostream>
 
class Shape {
public:
   virtual void virtualfunc() const {}
};
 
class Circle: public Shape {
public:
   virtual void virtualfunc() const {}
};
 
using namespace std;
int main() {
   Shape shape_instance;
   Shape& ref_shape = shape_instance;
   try {
      Circle& ref_circle = dynamic_cast<Circle&>(ref_shape);
   }
   catch (bad_cast b) {
      cout << "Caught: " << b.what();
   }
}
 
 

캐스팅될 개체[Shape]가 지정된 캐스트 형식[원]에서 파생되지 않으므로 예외가 throw됩니다. 예외를 방지하려면 main에 다음과 같은 선언을 추가합니다.

1
2
Circle circle_instance;
Circle& ref_circle = circle_instance;
 

그리고 나서 다음과 같이 try 블록의 캐스트 감각을 반전시킵니다.

1
Shape& ref_shape = dynamic_cast<Shape&>(ref_circle);
 

참고 항목

dynamic_cast 연산자
C++ 키워드
C++ 예외 처리

 

 

다음글

반응형
Posted by Library of Lotus
,