C++ typeid實現原理


最近看了boost::any類源碼,其實現主要依賴typeid操作符。很好奇這樣實現的時間和空間開銷有多大,決定探一下究竟。

VS2008附帶的type_info類只有頭文件,沒有源文件,聲明如下:

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. class type_info {  
  2. public:  
  3.     virtual ~type_info();  
  4.     _CRTIMP_PURE bool __CLR_OR_THIS_CALL operator==(const type_info& rhs) const;  
  5.     _CRTIMP_PURE bool __CLR_OR_THIS_CALL operator!=(const type_info& rhs) const;  
  6.     _CRTIMP_PURE int __CLR_OR_THIS_CALL before(const type_info& rhs) const;  
  7.     _CRTIMP_PURE const char* __CLR_OR_THIS_CALL name(__type_info_node* __ptype_info_node = &__type_info_root_node) const;  
  8.     _CRTIMP_PURE const char* __CLR_OR_THIS_CALL raw_name() const;  
  9. private:  
  10.     void *_m_data;  
  11.     char _m_d_name[1];  
  12.     __CLR_OR_THIS_CALL type_info(const type_info& rhs);  
  13.     type_info& __CLR_OR_THIS_CALL operator=(const type_info& rhs);  
  14.     _CRTIMP_PURE static const char *__CLRCALL_OR_CDECL _Name_base(const type_info *,__type_info_node* __ptype_info_node);  
  15.     _CRTIMP_PURE static void __CLRCALL_OR_CDECL _Type_info_dtor(type_info *);  
  16. };  

測試代碼:

 

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class Object  
  5. {  
  6. };  
  7.   
  8. int main()  
  9. {  
  10.     Object obj;  
  11.     cout << "type name:" << typeid(obj).name() << endl;  
  12.     cout << "type raw name:" << typeid(obj).raw_name() << endl;  
  13.     if(typeid(obj) == typeid(Object))  
  14.     {  
  15.         cout << "type is equal" << endl;  
  16.     }  
  17.     else  
  18.     {  
  19.         cout << "type is not equal" << endl;  
  20.     }  
  21.     return 0;  
  22. }  

 

 

輸出:

type name:class Object
type raw name:.?AVObject@@
type is equal

 

在解釋每個函數的實現原理前先開看type_info類的存儲方式。

typeid返回的是type_info的引用,這個類不能拷貝,也不能自己構造,所以每個類最多只有一個type_info的數據,這個數據存放在哪里的呢?

用UltraEdit打開exe文件,搜索“Object”,能找到這個字符串。再用PE工具打開這個exe,發現這個字符串屬於data節(這是可讀可寫的全局數據段)。再把有typeid的代碼都注釋,PE文件中沒有了這個字符串。得出一個結論:

編譯器會為每一種typeid操作的類型生成一份保存在數據段的type_info數據。

 

這份數據有多大呢?看下面這段代碼:

 

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class Object  
  5. {  
  6. };  
  7.   
  8. int main()  
  9. {  
  10.     const type_info* p = &typeid(Object);  
  11.     cout << p << endl;  
  12.     return 0;  
  13. }  

 

 

在cout那一行下斷點,查看到p的值為:

再看下這個類的聲明,析構函數為virtual類型的,所以p的頭四字節為虛函數表。p+4為_m_data,void*類型,四個字節,調試時發現都是0,還不清楚其表示什么。

p+8為_m_d_name,char類型數組,存儲的是raw_name,每種類型的raw_name大小不定長,所以數組長度為1。現在type_info的存儲結構已經一目了然:

每種類型的type_info數據長度依賴於類型名稱,至少9個字節。

現在假設一個復雜的工程里面有50個類型用了typeid操作符,平均每個type_info長度為24,這些數據增加的PE大小為1200B,就1K左右。而現在的PE動輒幾十M,所以這點空間開銷根本不算什么。

 

再看看這些函數調用的開銷:

  • raw_name函數直接返回_m_d_name的地址,非常快;
  • name函數將_m_d_name存儲的字符串解碼成實際的名稱,也是很快;
  • ==操作符是比較raw_name是否相等,也是很快。

讀者可能會有兩點疑惑:

  1. 存儲的時候為什么不直接存儲成name呢?我想最大的原因是節省空間,比如double的raw_name為".N",name為"double"多了四字節。
  2. ==操作符為什么不直接比較兩個type_info引用的地址是否相等呢?我也很疑惑,我看匯編碼發現它是比較raw_name。

備注:C++並沒有規定typeid實現標准,各個編譯器可能會不一樣,上述分析過程基於VS2008自帶的編譯器。

總結:typeid帶來的時間和空間開銷是非常小的,不過使用的時候盡量不要違背開放封閉原則。

http://blog.csdn.net/passion_wu128/article/details/38441633


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM