RTTI(Run Time Type Identification)即通過運行時類型識別,程序能夠使用基類的指針或引用來檢查著這些指針或引用所指的對象的實際派生類型。
為什麼會出現RTTI這一機制,這和C++語言本身有關系。和很多其他語言一樣,C++是一種靜態類型語言。其數據類型是在編譯期就確定的,不能在運行時更改。然而由於面向對象程序設計中多態性的要求,C++中的指針或引用(Reference)本身的類型,可能與它實際代表(指向或引用)的類型並不一致。有時我們需要將一個多態指針轉換為其實際指向對象的類型,就需要知道運行時的類型信息,這就產生了運行時類型識別的要求。和Java相比,C++要想獲得運行時類型信息,只能通過RTTI機制,並且C++最終生成的代碼是直接與機器相關的。
我對Java的運行時類型識別不是很熟悉,所以查了一下相關資料:Java中任何一個類都可以通過反射機制來獲取類的基本信息(接口、父類、方法、屬性、Annotation等),而且Java中還提供了一個關鍵字,可以在運行時判斷一個類是不是另一個類的子類或者是該類的對象,Java可以生成字節碼文件,再由JVM(Java虛擬機)加載運行,字節碼文件中可以含有類的信息。
RTTI提供了兩個非常有用的操作符:typeid和dynamic_cast。
typeid操作符,返回指針和引用所指的實際類型;
dynamic_cast操作符,將基類類型的指針或引用安全地轉換為其派生類類型的指針或引用。
我們知道C++的多態性(運行時)是由虛函數實現的,對於多態性的對象,無法在程序編譯階段確定對象的類型。當類中含有虛函數時,其基類的指針就可以指向任何派生類的對象,這時就有可能不知道基類指針到底指向的是哪個對象的情況,類型的確定要在運行時利用運行時類型標識做出。為了獲得一個對象的類型可以使用typeid函數,該函數反回一個對type_info類對象的引用,要使用typeid必須使用頭文件
下面是typeinfo的源代碼:
/*** *typeinfo.h - Defines the type_info structure and exceptions used for RTTI * * Copyright (c) Microsoft Corporation. All rights reserved. * Modified January 1996 by P.J. Plauger * *Purpose: * Defines the type_info structure and exceptions used for * Runtime Type Identification. * * [Public] * ****/ #pragma once #ifndef _TYPEINFO_ #define _TYPEINFO_ #ifndef RC_INVOKED #include#include // for type_info::hash_code() #pragma pack(push,_CRT_PACKING) #pragma warning(push,3) #pragma push_macro("new") #undef new #pragma warning(disable: 4275) #ifndef __cplusplus #error This header requires a C++ compiler ... #endif #if !defined(_WIN32) #error ERROR: Only Win32 target supported! #endif struct __type_info_node { void *_MemPtr; __type_info_node* _Next; }; extern __type_info_node __type_info_root_node; class type_info { public: size_t hash_code() const _THROW0() { // hash name() to size_t value by pseudorandomizing transform return (_STD _Hash_seq((const unsigned char *) name(), _CSTD strlen(name()))); } #if defined(CRTDLL) && defined(_CRTBLD) _CRTIMP_PURE #endif #ifdef _M_CEE [System::Security::SecurityCritical] #endif virtual ~type_info() _NOEXCEPT; #if defined(_SYSCRT) _CRTIMP_PURE int __CLR_OR_THIS_CALL operator==(const type_info& _Rhs) const; _CRTIMP_PURE int __CLR_OR_THIS_CALL operator!=(const type_info& _Rhs) const; #else _CRTIMP_PURE bool __CLR_OR_THIS_CALL operator==(const type_info& _Rhs) const; _CRTIMP_PURE bool __CLR_OR_THIS_CALL operator!=(const type_info& _Rhs) const; #endif _CRTIMP_PURE bool __CLR_OR_THIS_CALL before(const type_info& _Rhs) const; _CRTIMP_PURE const char* __CLR_OR_THIS_CALL name(__type_info_node* __ptype_info_node = &__type_info_root_node) const; _CRTIMP_PURE const char* __CLR_OR_THIS_CALL raw_name() const; private: void *_M_data; char _M_d_name[1]; #if defined(_CRTBLD) /* TRANSITION */ __CLR_OR_THIS_CALL type_info(const type_info& _Rhs); type_info& __CLR_OR_THIS_CALL operator=(const type_info& _Rhs); #else public: __CLR_OR_THIS_CALL type_info(const type_info&) = delete; type_info& __CLR_OR_THIS_CALL operator=(const type_info&) = delete; private: #endif _CRTIMP_PURE static const char *__CLRCALL_OR_CDECL _Name_base(const type_info *,__type_info_node* __ptype_info_node); _CRTIMP_PURE static void __CLRCALL_OR_CDECL _Type_info_dtor(type_info *); #if defined(_CRTBLD) #if !defined(_SYSCRT) _CRTIMP_PURE static const char *__CLRCALL_OR_CDECL _Name_base_internal(const type_info *,__type_info_node* __ptype_info_node); _CRTIMP_PURE static void __CLRCALL_OR_CDECL _Type_info_dtor_internal(type_info *); public: // CRT dll import libs alias non _internal to _internal. These method definitions are // only used within the crtdll to provide targets for aliasobj in the crt import lib. _CRTIMP_PURE void __CLR_OR_THIS_CALL _type_info_dtor_internal_method(void); _CRTIMP_PURE const char* __CLR_OR_THIS_CALL _name_internal_method(__type_info_node* __ptype_info_node) const; #endif #endif }; #if _HAS_EXCEPTIONS _STD_BEGIN using ::type_info; _STD_END #if !defined(_CRTBLD) || !defined(_TICORE) // This include must occur below the definition of class type_info #include _STD_BEGIN class _CRTIMP_PURE bad_cast : public exception { public: #ifdef _M_CEE_PURE __CLR_OR_THIS_CALL bad_cast(const char * _Message = "bad cast") : exception(_Message) {} __CLR_OR_THIS_CALL bad_cast(const bad_cast &_That) : exception(_That) {} virtual __CLR_OR_THIS_CALL ~bad_cast() _NOEXCEPT {} #if defined(_CRTBLD) && defined(CRTDLL) private: // This is aliased to public:bad_cast(const char * const &) to provide // the old, non-conformant constructor. __CLR_OR_THIS_CALL bad_cast(const char * const * _Message) : exception((const char *)_Message) { } #endif /* _CRTBLD && CRTDLL */ #else /* _M_CEE_PURE */ __CLR_OR_THIS_CALL bad_cast(const char * _Message = "bad cast"); __CLR_OR_THIS_CALL bad_cast(const bad_cast &); virtual __CLR_OR_THIS_CALL ~bad_cast() _NOEXCEPT; #if defined(_CRTBLD) && defined(CRTDLL) private: // This is aliased to public:bad_cast(const char * const &) to provide // the old, non-conformant constructor. __CLR_OR_THIS_CALL bad_cast(const char * const * _Message); #endif /* _CRTBLD && CRTDLL */ #endif /* _M_CEE_PURE */ }; class _CRTIMP_PURE bad_typeid : public exception { public: #ifdef _M_CEE_PURE __CLR_OR_THIS_CALL bad_typeid(const char * _Message = "bad typeid") : exception(_Message) {} __CLR_OR_THIS_CALL bad_typeid(const bad_typeid &_That) : exception(_That) {} virtual __CLR_OR_THIS_CALL ~bad_typeid() _NOEXCEPT {} #else /* _M_CEE_PURE */ __CLR_OR_THIS_CALL bad_typeid(const char * _Message = "bad typeid"); __CLR_OR_THIS_CALL bad_typeid(const bad_typeid &); virtual __CLR_OR_THIS_CALL ~bad_typeid() _NOEXCEPT; #endif /* _M_CEE_PURE */ }; class _CRTIMP_PURE __non_rtti_object : public bad_typeid { public: #ifdef _M_CEE_PURE __CLR_OR_THIS_CALL __non_rtti_object(const char * _Message) : bad_typeid(_Message) {} __CLR_OR_THIS_CALL __non_rtti_object(const __non_rtti_object &_That) : bad_typeid(_That) {} virtual __CLR_OR_THIS_CALL ~__non_rtti_object() _NOEXCEPT {} #else /* _M_CEE_PURE */ __CLR_OR_THIS_CALL __non_rtti_object(const char * _Message); __CLR_OR_THIS_CALL __non_rtti_object(const __non_rtti_object &); virtual __CLR_OR_THIS_CALL ~__non_rtti_object() _NOEXCEPT; #endif /* _M_CEE_PURE */ }; _STD_END #endif // !_CRTBLD || !_TICORE #else _STD_BEGIN // CLASS bad_cast class _CRTIMP2 bad_cast : public exception { // base of all bad cast exceptions public: bad_cast(const char *_Message = "bad cast") _THROW0() : exception(_Message) { // construct from message string } virtual ~bad_cast() _NOEXCEPT { // destroy the object } protected: virtual void _Doraise() const { // perform class-specific exception handling _RAISE(*this); } }; // CLASS bad_typeid class _CRTIMP2 bad_typeid : public exception { // base of all bad typeid exceptions public: bad_typeid(const char *_Message = "bad typeid") _THROW0() : exception(_Message) { // construct from message string } virtual ~bad_typeid() _NOEXCEPT { // destroy the object } protected: virtual void _Doraise() const { // perform class-specific exception handling _RAISE(*this); } }; class _CRTIMP2 __non_rtti_object : public bad_typeid { // report a non RTTI object public: __non_rtti_object(const char *_Message) : bad_typeid(_Message) { // construct from message string } }; _STD_END #endif /* _HAS_EXCEPTIONS */ #endif /* RC_INVOKED */ #pragma pop_macro("new") #pragma pack(pop) #pragma warning(pop) #endif // _TYPEINFO_ /* * Copyright (c) Microsoft Corporation. ALL RIGHTS RESERVED. * Modified January 1996 by P.J. Plauger * Modified November 1998 by P.J. Plauger * Consult your license regarding permissions and restrictions. V6.00:0009 */
對於源碼可以簡單解釋為:
class type_info { public: //析構函數 _CRTIMP virtual ~type_info(); //重載的==操作符 _CRTIMP int operator==(const type_info& rhs) const; //重載的!=操作符 _CRTIMP int operator!=(const type_info& rhs) const; _CRTIMP int before(const type_info& rhs) const;//用於type_info對象之間的排序算法 //返回類的名字 _CRTIMP const char* name() const; _CRTIMP const char* raw_name() const;//返回類名稱的編碼字符串 private: //各種存儲數據成員 void *_m_data; char _m_d_name[1]; //將拷貝構造函數與賦值構造函數設為了私有 type_info(const type_info& rhs); type_info& operator=(const type_info& rhs); };
因為type_info類的復制構造函數和賦值運算符都是私有的,所以不允許用戶自已創建type_info的類。唯一要使用type_info類的方法就是使用typeid函數。
typeid函數的主要作用就是讓用戶知道當前的變量是什麼類型的,比如使用typeid(a).name()就能知道變量a是什麼類型的。typeid()函數的返回類型為typeinfo類型的引用。
typeid函數是type_info類的一個引用對象,可以訪問type_info類的成員。但因為不能創建type_info類的對象,而typeid又必須反回一個類型為type_info類型的對象的引用,所以怎樣在typeid函數中創建一個type_info類的對象以便讓函數反回type_info類對象的引用就成了問題。這可能是把typid函數聲明為了type_info類的友元函數來實現的,默認構造函數並不能阻止該類的友元函數創建該類的對象。所以typeid函數如果是友元的話就可以訪問type_info類的私有成員,從而可以創建type_info類的對象,從而可以創建返回類型為type_info類的引用。
例如:
class A{ private: A(){} A(const A&){} A& operator = (const A&){} friend A& f(); };
函數f()是類A的友元,所以在函數f()中可以創建類A的對象。同時為了實現函數f()反回的對象類型是A的引用,就必須在函數f中創建一個類A的對象以作為函數f的反回值,比如函數f可以這樣定義:
A &f() { A m_a; return m_a; }
因為typeid函數是type_info類的對象,也就是說可以用該函數訪問type_info類的成員,即type_info類中重載的==和!=運算符,name()和before()成員函數,比如typid(a).name()和typid(a)==typid(b)等等。
class A{ private: A(){ b = 3; cout << "A\n"; } public: void name() { cout << "Class Name is A\n"; } friend A &f(); private: int b; }; A &f() { A friend_A; cout << "The function of Class A\n"; return friend_A; } int main() { f().name(); return 0; }
運行截圖:
函數f()是類A的友元,且返回一個類A的對象,因為f()函數是類A的友元,所以在函數f中可以用默認構造函數創建類A的對象,這時函數f()同時是一個函數,也是類A的對象,因此也可以訪問類A中的成員。
typeid函數的使用示例:
class A{ private: int a; }; class B :public A{ public: virtual void f(){ cout << "HelloWorld\n"; } private: int b; }; class C :public B{ public: virtual void f(){ cout << "HelloWorld++\n"; } private: int c; }; class D :public A{ public: virtual void f(){ cout << "HelloWorld--\n"; } private: int d; }; int main() { int a = 2; cout << typeid(a).name() << endl; A objA; //打印出class A cout << typeid(objA).name() << endl; B objB; //打印出class B cout << typeid(objB).name() << endl; C objC; //打印出class C cout << typeid(objC).name() << endl; //以下是多態在VC 6.0編譯器不支持,但是在GCC以及微軟更高版本的編譯器卻都是 //支持的,且是在運行時候來確定類型的,而不是在編譯器,會打印出class c B *ptrB=new C(); cout<運行截圖:
dynamic_cast強制轉換運算符
該轉換符用於將一個指向派生類的基類指針或引用轉換為派生類的指針或引用,注意dynamic_cast轉換符只能用於含有虛函數的類,其表達式為dynamic_cast<類型>(表達式),其中的類型是指把表達式要轉換成的目標類型,比如含有虛函數的基類B和從基類B派生出的派生類D,則B *pb; D *pd, md; pb=&md; pd=dynamic
(pb); 最後一條語句表示把指向派生類D的基類指針pb轉換為派生類D的指針,然後將這個指針賦給派生類D的指針pd,有人可能會覺得這樣做沒有意義,既然指針pd要指向派生類為什麼不pd=&md;這樣做更直接呢? 因為有些時候我們需要強制轉換,比如如果指向派生類的基類指針B想訪問派生類D中的除虛函數之外的成員時就需要把該指針轉換為指向派生類D的指針,以達到訪問派生類D中特有的成員的目的,比如派生類D中含有特有的成員函數g(),這時可以這樣來訪問該成員dynamic_cast
(pb)->g();因為dynamic_cast轉換後的結果是一個指向派生類的指針,所以可以這樣訪問派生類中特有的成員。但是該語句不影響原來的指針的類型,即基類指針pb仍然是指向基類B的。 dynamic_cast轉換符只能用於指針或者引用。dynamic_cast轉換符只能用於含有虛函數的類。dynamic_cast轉換操作符在執行類型轉換時首先將檢查能否成功轉換,如果能成功轉換則轉換之,如果轉換失敗,如果是指針則反回一個0值,如果是轉換的是引用,則拋出一個bad_cast異常,所以在使用dynamic_cast轉換之間應使用if語句對其轉換成功與否進行測試,比如pd=dynamic_cast<D*>(pb); if(pd){…}else{…},或者這樣測試if(dynamic_cast
(pb)){…}else{…}。 對於其它的強制轉換運算符:static_cast,reinterpret_cast,const_cast。
寫代碼是一種藝術,甚於蒙娜麗莎的微笑。