這是RTTI(運行階段類型識別)的問題,c++有三個支持RTTI的元素:
1. dynamic_cast 操作符
如果可能的話,dynamic_cast操作符將使用一個指向基類的指針來生成一個指向派生類的指針;否則,該操作符返回空指針。這是最常用的 RTTI組件,它不能回答“指針指向的是哪類對象”這樣的問題,但他能回答“是否可以安全地將對象的地址賦給特定類型的指針”這樣的問題。如:
class A{}
class B: public A{}
class C: public B{}
然后有下面的指針:
A *a = new A;
B *b = new B;
C *c = new C;
則:
C *cc1 = dynamic_cast<C*>(c); //安全
C *cc2 = dynamic_cast<C*>(a); //cc2是空指針
C *cc3 = dynamic_cast<c*>(b); //cc3是空指針
B *bb = dynamic_cast<C*>(b); //安全
注:只能將此RTTI用於包含虛函數的類層次結構,原因在於只有對於這種類層次結構,才應該將派生類對象的地址賦給基類指針。
2. typeid操作符
3. type_info結構,(須包含頭文件<typeinfo>)
class A{}
class B: public A{}
class C: public B{}
然后有下面的指針:
A *pa = new A;
B *pb = new B;
C *pc = new C;
則:
typeid(C) == typeid(pc); //值為true
typeid(B) == typeid(pc); //值為false
type_info有一個name()成員函數,返回一個類名的字符串:
cout << typeid(*pc).name(); //打印C類的類名
注:typeid操作符和name()成員函數都適用於dynamic_cast和virtual函數不能處理的情況
以下總結:
轉自博客:http://www.cppblog.com/smagle/archive/2010/05/14/115286.html
開typeid神秘面紗之前,我們先來了解一下RTTI(Run-Time Type Identification,運行時類型識別),它使程序能夠獲取由基指針或引用所指向的對象的實際派生類型,即允許“用指向基類的指針或引用來操作對 象”的程序能夠獲取到“這些指針或引用所指對象”的實際派生類型。在C++中,為了支持RTTI提供了兩個操作符:dynamic_cast和 typeid。
dynamic_cast允許運行時刻進行類型轉換,從而使程序能夠在一個類層次結構中安全地轉化類型,與之相對應的還有一個非安全的轉換操作符 static_cast,因為這不是本文的討論重點,所以這里不再詳述,感興趣的可以自行查閱資料。下面就開始今天我們的話題:typeid。
typeid是C++的關鍵字之一,等同於sizeof這類的操作符。typeid操作符的返回結果是名為type_info的標准庫類型的對象的引用(在頭文件typeinfo中定義,稍后我們看一下vs和gcc庫里面的源碼),它的表達式有下圖兩種形式。
如果表達式的類型是類類型且至少包含有一個虛函數,則typeid操作符返回表達式的動態類型,需要在運行時計算;否則,typeid操作符返回表達式的靜態類型,在編譯時就可以計算。
ISO C++標准並沒有確切定義type_info,它的確切定義編譯器相關的,但是標准卻規定了其實現必需提供如下四種操作(在之后的章節中我會來分析type_info類文件的源碼):
| t1 == t2 | 如果兩個對象t1和t2類型相同,則返回true;否則返回false |
| t1 != t2 | 如果兩個對象t1和t2類型不同,則返回true;否則返回false |
| t.name() | 返回類型的C-style字符串,類型名字用系統相關的方法產生 |
| t1.before(t2) | 返回指出t1是否出現在t2之前的bool值 |
type_info類提供了public虛 析構函數,以使用戶能夠用其作為基類。它的默認構造函數和拷貝構造函數及賦值操作符都定義為private,所以不能定義或復制type_info類型的 對象。程序中創建type_info對象的唯一方法是使用typeid操作符(由此可見,如果把typeid看作函數的話,其應該是type_info的 友元)。type_info的name成員函數返回C-style的字符串,用來表示相應的類型名,但務必注意這個返回的類型名與程序中使用的相應類型名 並不一定一致(往往如此,見后面的程序),這具體由編譯器的實現所決定的,標准只要求實現為每個類型返回唯一的字符串。
測試程序如下:
1 #include <iostream> 2 using namespace std; 3 4 class Base {}; 5 class Derived: public Base {}; 6 7 int main() 8 { 9 Base b, *pb; 10 pb = NULL; 11 Derived d; 12 13 cout << typeid(int).name() << endl 14 << typeid(unsigned).name() << endl 15 << typeid(long).name() << endl 16 << typeid(unsigned long).name() << endl 17 << typeid(char).name() << endl 18 << typeid(unsigned char).name() << endl 19 << typeid(float).name() << endl 20 << typeid(double).name() << endl 21 << typeid(string).name() << endl 22 << typeid(Base).name() << endl 23 << typeid(b).name()<<endl 24 << typeid(pb).name()<<endl 25 << typeid(Derived).name() << endl 26 << typeid(d).name()<<endl 27 << typeid(type_info).name() << endl; 28 29 return 0; 30 }
不同的編譯器輸出如下:用MS的V8和GUN的GCC編譯該段代碼並運行,結果分別為下面的左右二圖

對比代碼以及上面的文字描述,不知道各位是否已經有所明了(這里需要注意的是Base類的對象b和對象指針pb,他們的輸出)。
考慮到V8的輸出很直觀,所以我采用V8來做實驗。下面對上面的代碼稍微添加一點內容,如下:
Base *pb2 = dynamic_cast<Base *>(new Derived); Base &b2 = d; Base *pb3 = &d; cout << typeid(pb2).name() <<endl//輸出Base * << typeid(b2).name()<<endl //輸出Base << typeid(pb3).name()<<endl//輸出Base * << typeid(*pb3).name()<<endl;//輸出Base
這次Base含有虛函數,注意看結果,指針仍然是Base*的,盡管他們指向的是底層對象Derived,而這些Base對象的類型卻是Derived的。
因為指針pb3不是類類型,所以typeid就返回該指針pb3的指針類型Base *。而*pb3是一個類類型的表達式,而且該類帶有虛函數,所以指出該pb3指向的底層對象的類型Derived。
如果typeid操作符的操作數是至少包含一個虛擬函數的類類型時,並且該表達式是一個基類的引用,則typeid操作符指出底層對象的派生類類型。
