typeid詳解

TuxedoLinux發表於2018-06-07
    在揭開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<<endl
    
<< typeid(Base*<<endl
    
<< typeid(&pb3) <<endl;

感謝: http://blog.programfan.com/article.asp?id=45931