轉載網址:http://www.cppblog.com/smagle/archive/2010/05/14/115286.aspx
在揭開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的字串,用來表示相應的類型名,但務必注意這個返回的類型名與程式中使用的相應類型名並不一定一致(往往如此,見後面的程式),這具體由編譯器的實現所決定的,標準只要求實現為每個類型返回唯一的字串。
上面的都是一些理論的東西,看不真切,下面將通過代碼和圖例來展示。
#include <iostream>
using namespace std;
class Base {};
class Derived: public Base {};
int main()
{
Base b, *pb;
pb = NULL;
Derived d;
cout << typeid(int).name()
<< endl
<< typeid(unsigned).name()
<< endl
<< typeid(long).name()
<< endl
<< typeid(unsigned long).name()
<< endl
<< typeid(char).name()
<< endl
<< typeid(unsigned char).name()
<< endl
<< typeid(float).name()
<< endl
<< typeid(double).name()
<< endl
<< typeid(string).name()
<< endl
<< typeid(Base).name()
<< endl
<< typeid(b).name()<<endl
<< typeid(pb).name()<<endl
<< typeid(Derived).name() << endl
<< typeid(d).name()<<endl
<< typeid(type_info).name()
<< endl;
return 0;
}
我分別用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不包含虛函數,所以typeid的結果指出,運算式的類型是Base或Base *型,儘管他們的底層對象是Derived。即:當typeid操作符的運算元是不帶有虛函數的類類型時,typeid操作符會指出運算元的類型,而不是底層對象的類型。
下面在對Base函數做一個小小調整,為其加上一個虛函數,再看輸出結果。
class Base {virtual
void f(){}; };
/*...*/
cout << typeid(pb2).name()
<<endl//輸出Base *
<< typeid(b2).name()<<endl //輸出Derived
<< typeid(pb3).name()<<endl//輸出Base *
<< typeid(*pb3).name()<<endl;//輸出Derived
這次Base含有虛函數,注意看結果,指標仍然是Base*的,儘管他們指向的是底層對象Derived,而這些Base對象的類型卻是Derived的。
因為指標pb3不是類類型,所以typeid就返回該指標pb3的指標類型Base *。而*pb3是一個類類型的運算式,而且該類帶有虛函數,所以指出該pb3指向的底層對象的類型Derived。
如果typeid操作符的運算元是至少包含一個虛擬函數的類類型時,並且該運算式是一個基類的應用,則typeid操作符指出底層對象的衍生類別類型。
好了,文篇到此結束,留下幾道小題目吧。
//採用V8環境
cout<<typeid(7.84).name()<<endl
<< typeid(Base*).name()<<endl
<< typeid(&pb3).name()<<endl;