RTTI和類型轉換,RTTI類型轉換
RTTI通過運行時類型識別(RTTI),程式能夠使用基類的指標或引用來檢索這些指標或引用所指對象的實際衍生類別型。通過下面兩個操作符提供 RTTI:1. typeid 操作符,返回指標或引用所指對象的實際類型。2. dynamic_cast 操作符,將基類類型的指標或引用安全地轉換為衍生類別型的指標或引用。這些操作符只為帶有一個或多個虛函數的類返回動態類型資訊,對於其他類型,返回靜態(即編譯時間)類型的資訊。對於帶虛函數的類,在運行時執行 RTTI 操作符,但對於其他類型,在編譯時間計算 RTTI 操作符。當具有基類的引用或指標,但需要執行不是基類組成部分的衍生類別操作的時候,需要動態強制類型轉換。通常,從基類指標獲得衍生類別行為最好的方法是通過虛函數。當使用虛函數的時候,編譯器自動根據對象的實際類型選擇正確的函數。但是,在某些情況下,不可能使用虛函數。在這些情況下,RTTI 提供了可選的機制。然而,這種機制比使用虛函數更容易出錯:程式員必須知道應該將對象強制轉換為哪種類型,並且必須檢查轉換是否成功執行了。使用動態強制類型轉換要小心。只要有可能,定義和使用虛函數比直接接管類型管理好得多。
dynamic_cast將基類類型對象的引用或指標轉換為同一繼承層次中其他類型的引用或指標。註:傳入dynamic_cast的參數對應的類,必須帶有virtual函數,否則,編譯無法通過。dynamic_cast 涉及運行時類型檢查。如果綁定到引用或指標的對象不是目標類型的對象,則 dynamic_cast 失敗。如果轉換到指標類型的 dynamic_cast 失敗,則 dynamic_cast 的結果是 0 值;如果轉換到參考型別的 dynamic_cast 失敗,則拋出一個 bad_cast 類型的異常。
typeid 操作符typeid 運算式形如:typeid(e);這裡 e 是任意運算式或者是類型名。如果運算式的類型是類類型且該類包含一個或多個虛函數,則運算式的動態類型可能不同於它的靜態編譯時間類型;typeid 操作符可以與任何類型的運算式一起使用。內建類型的運算式以及常量都可以用作 typeid 操作符的運算元。如果運算元不是類類型或者是沒有虛函數的類,則 typeid 操作符指出運算元的靜態類型;如果運算元是定義了至少一個虛函數的類類型,則在運行時計算類型。
typeid 操作符的結果是名為 type_info 的標準庫類型的對象引用,要使用 type_info 類,必須包含庫標頭檔typeinfo。typeid 最常見的用途是比較兩個運算式的類型,或者將運算式的類型與特定類型相比較:只有當 typeid 的運算元是帶虛函數的類類型的對象的時候,才返回動態類型資訊。測試指標(相對於指標指向的對象)返回指標的靜態、編譯時間類型。如果指標 p 的值是 0,那麼,如果 p 的類型是帶虛函數的類型,則typeid(*p) 拋出一個 bad_typeid 異常;如果 p 的類型沒有定義任何虛函數,則結果與 p 的值是不相關的。正像計算運算式 sizeof一樣,編譯器不計算 *p,它使用 p 的靜態類型,這並不要求 p 本身是有效指標。
執行個體
#include <iostream>#include <typeinfo>using namespace std;class Base{ public: virtual void func(){cout << "I, Base." << endl;}};class Derived:public Base{ public: void dfunc(){cout << "I, Derived." << endl;}};class BadDerived{ public: virtual void func(){cout << "I, BadDerived." << endl;}};class T{ public: void dfunc(){cout << "I, T." << endl;}};int main(){ Base *baseptr = new Derived; BadDerived *badptr = new BadDerived; cout << endl << "*********Test 0************" << endl; if(T *deriveptr = dynamic_cast<T*>(baseptr)) { cout << "success---->"; deriveptr->dfunc(); } else { cout << "failed----->"; baseptr->func(); cout << "DEBUG" << endl; cout << "typeid(T).name = " << typeid(T).name() << endl; cout << "typeid(Baseref).name = " << typeid(*baseptr).name() << endl; } cout << endl << "*********Test 1************" << endl; if(Derived *deriveptr = dynamic_cast<Derived*>(baseptr)) { cout << "success---->"; deriveptr->dfunc(); } else { cout << "failed----->"; baseptr->func(); cout << "DEBUG" << endl; cout << "typeid(Derived).name = " << typeid(Derived).name() << endl; cout << "typeid(Baseref).name = " << typeid(*baseptr).name() << endl; } cout << endl << "*********Test 2************" << endl; if(Derived *deriveptr = dynamic_cast<Derived*>(badptr)) { cout << "success---->"; deriveptr->dfunc(); } else { cout << "failed----->"; badptr->func(); cout << "DEBUG" << endl; cout << "typeid(Derived).name = " << typeid(Derived).name() << endl; cout << "typeid(Baseref).name = " << typeid(*badptr).name() << endl; } Derived deriveobj; Base &baseref = deriveobj; BadDerived badobj; BadDerived &badref = badobj; cout << endl << "*********Test 3************" << endl; try { Derived &deriveref = dynamic_cast<Derived&>(baseref); cout << "success---->"; deriveref.dfunc(); } catch(bad_cast) { cout << "failed----->"; baseref.func(); cout << "DEBUG" << endl; cout << "typeid(Derived).name = " << typeid(Derived).name() << endl; cout << "typeid(Baseref).name = " << typeid(baseref).name() << endl; } cout << endl << "*********Test 4************" << endl; try { Derived &deriveref = dynamic_cast<Derived&>(badref); cout << "success---->"; deriveref.dfunc(); } catch(bad_cast) { cout << "failed----->"; baseref.func(); cout << "DEBUG" << endl; cout << "typeid(Derived).name = " << typeid(Derived).name() << endl; cout << "typeid(Baseref).name = " << typeid(badref).name() << endl; } cout << endl << "*********Test 5************" << endl; if(typeid(Derived) == typeid(baseref)) { Derived &deriveref = dynamic_cast<Derived&>(baseref); cout << "success---->"; deriveref.dfunc(); } else { cout << "failed----->"; baseref.func(); cout << "DEBUG" << endl; cout << "typeid(Derived).name = " << typeid(Derived).name() << endl; cout << "typeid(Baseref).name = " << typeid(baseref).name() << endl; } cout << endl << "*********Test 6************" << endl; if(typeid(Derived) == typeid(badref)) { Derived &deriveref = dynamic_cast<Derived&>(badref); cout << "success---->"; deriveref.dfunc(); } else { cout << "failed----->"; badref.func(); cout << "DEBUG" << endl; cout << "typeid(Derived).name = " << typeid(Derived).name() << endl; cout << "typeid(Baseref).name = " << typeid(badref).name() << endl; }}
執行結果*********Test 0************
failed----->I, Base.
DEBUG
typeid(T).name = 1T
typeid(Baseref).name = 7Derived
*********Test 1************
success---->I, Derived.
*********Test 2************
failed----->I, BadDerived.
DEBUG
typeid(Derived).name = 7Derived
typeid(Baseref).name = 10BadDerived
*********Test 3************
success---->I, Derived.
*********Test 4************
failed----->I, Base.
DEBUG
typeid(Derived).name = 7Derived
typeid(Baseref).name = 10BadDerived
*********Test 5************
success---->I, Derived.
*********Test 6************
failed----->I, BadDerived.
DEBUG
typeid(Derived).name = 7Derived
typeid(Baseref).name = 10BadDerived
類型轉換
C風格轉換是“萬能的轉換”,但需要程式員把握轉換的安全性,編譯器無能為力。
類型轉換的不安全來源於兩個方面:其一是類型的窄化轉化,會導致資料位元數的丟失;其二是在類繼承鏈中,將父類對象的地址(指標)強制轉化成子類的地址(指標),這就是所謂的下行轉換,“下”表示沿著繼承鏈向下走(向子類的方向走)。類似地,上行轉換的“上”表示沿繼承鏈向上走(向父類的方向走);上行轉換一般是安全的,下行轉換很可能是不安全的;因為子類中包含父類,所以上行轉換(只能調用父類的方法,引用父類的成員變數)一般是安全的。但父類中卻沒有子類的任何資訊,而下行轉換會調用到子類的方法、引用子類的成員變數,這些父類都沒有,所以很容易“指鹿為馬”或者乾脆指向不存在的記憶體空間。static_cast <new_type> (expression) 靜態轉換
static_cast最接近於C風格轉換,但在無關類指標轉換時,編譯器會報錯,提升了安全性。dynamic_cast <new_type> (expression) 動態轉換動態轉換確保類指標的轉換是合適完整的,它有兩個重要的約束條件,其一是要求new_type為指標或引用,其二是下行轉換時要求基類是多態的(基類中包含至少一個虛函數),如果發現下行轉換不安全,dynamic_cast返回一個null指標。當待轉換指標是void*或者轉換目標指標是void*時,dynamic_cast總是認為是安全的。reinterpret_cast <new_type> (expression) 重解釋轉換這個轉換是最“不安全”的,兩個沒有任何關係的類指標之間轉換都可以用這個轉換實現。reinterpret_cast可以把整型數轉換成地址(指標),這種轉換在系統底層的操作,有極強的平台依賴性,移植性不好;它同樣要求new_type是指標或引用。const_cast <new_type> (expression) 常量向非常量轉換const_cast可以將常量轉成非常量,但不會破壞原常量的const屬性,只是返回一個去掉const的變數。從char *cc = const_cast<char *>(c)可以看出了這個轉換的作用了,但切記,這個轉換並不轉換原常量本身,即c還是常量,只是它返回的結果cc是非常量了。