C++學習之顯示類型轉換與運行時類型識別RTTI
static_cast
const_cast
reinterpret_cast
運行時類型識別(RTTI)
dynamic_cast
哪種情況下dynamic_cast和static_cast使用的情況一樣?
什麼情況下使用dynamic_cast代替虛函數?
typeid
命名的強制類型轉換形式如下:
cast_name<type>(expression);
其中:cast_name指static_cast、dynamic_cast、const_cast、reinterpret_cast中的一種;
type指要轉換的目標類型;
expression指要轉換的值或表達式。
static_cast:任何具有明確定義的類型轉換,只要不包含底層const,都可以使用static_cast。同時,對於編譯器無法自動執行的類型轉換也非常有用。static_cast不能轉換掉expression的const、volitale、或者__unaligned屬性。沒有運行時類型檢查來保證轉換的安全性。
它主要有如下幾種用法:
①用於類層次結構中基類和子類之間指針或引用的轉換。
進行上行轉換(把子類的指針或引用轉換成基類表示)是安全的;
進行下行轉換(把基類指針或引用轉換成子類表示)時,由於沒有動態類型檢查,所以是不安全的。
②用於基本數據類型之間的轉換。如把int轉換成char,把int轉換成enum。這種轉換的安全性也要開發人員來保證。
③把空指針轉換成目標類型的空指針。
④把任何類型的表達式轉換成void類型。
如:
【代碼1】
double *d;
void* p = &d; //正確,任何"非常量對象"的地址都能存入void*
double* dp = static_cast<double*>(p);//正確,將void*轉換為初始指針類型
const_cast:只改變運算對象的底層const性質,不改變表達式(運算對象)的類型。可用於增加/去除運算對象的const屬性。同時,只能使用const_cast來進行更改const屬性,其他任何形式的命名強制類型轉換都會引起編譯器錯誤。如:
【代碼2】
const char *pc;
char *p = const_cast<char*>(pc);//正確:但通過p寫值是未定義的行為
char *q = static_cast<char*>(pc);//錯誤:static_cast不能轉換const性質
static_cast<string>(pc);//正確:字符串字面值轉換為string類型
const_cast<string>(pc); //錯誤:const_cast只用來改變常量屬性
const_cast常常用於函數重載的上下文中。如:
【代碼3】
//比較兩個string對象的長度,返回較短的那個引用
const string &shorterString(const string &s1, const string &s2){
return s1.size()<=s2.size() ? s1 : s2;
}
string & shorterString(string &s1, string &s2){
//調用const版本,先將s1和s2轉換為const版本,返回的引用 r 為const版本
auto &r = shorterString(const_cast<const string&>(s1),
const_cast<const string&>(s2));
return const_cast<string&>(r); //再次使用const_cast消除掉r的const屬性,得到一個普通的引用
}
reinterpret_cast:C++ Primer中解釋:通常為運算對象的位模式提供較低層次上的重新解釋。不懂~~有如下例子:
【代碼4】
int *ip;
char *pc = reinterpret_cast<char*>(ip);
在代碼4中,必須牢記pc實際上指向的是一個int而不是字符,如果把pc當成普通的字符指針使用就可能在運行時發生錯誤。如:
1
string str(pc); //錯誤!
謹記:使用reinterpret_cast是非常危險的!要想安全的使用,必須對涉及的類型和編譯器實現轉換的過程都非常了解。所以,還是盡量不要使用的好!
同時,所有的強制類型轉換,能不使用的情況盡量不要使用,因為其干擾了正常的類型檢查。
運行時類型識別(RTTI):(由兩個運算符實現)
typeid運算符,用來返回表達式的類型;
dynamic_cast運算符,用於將基類的指針或引用安全的轉換成派生類的指針或引用。
這兩個運算符特別適用於以下情況:使用‘基類對象’的‘指針或引用’執行某個‘派生類操作’並且’該操作‘不是‘虛函數’。
dynamic_cast運算符的使用形式:
//以下形式中:type必須為一個類類型,且通常該類型應該含有虛函數
dynamic_cast<type *>(e); //e必須是一個有效的指針;轉換失敗時結果為0
dynamic_cast<type &>(e); //e必須為左值;轉換失敗時拋出bad_cast異常
dynamic_cast<type &&>(e); //e不能為左值;轉換失敗時拋出bad_cast異常
其中,e的類型必須滿足以下三個條件中的任一個:
e的類型是目標type的公有派生類;
e的類型是目標type的公有基類;
e的類型是目標type的類型。
如果不滿足上述三個中的任一個條件,則轉換失敗。同時,當使用dynamic_cast對一個空指針執行轉換時,結果是所需類型的空指針。
dynamic_cast主要用於類層次間的上行轉換和下行轉換,還可以用於類之間的交叉轉換。
在類層次間進行上行轉換時,dynamic_cast和static_cast的效果是一樣的;在進行下行轉換時,dynamic_cast具有類型檢查的功能,比static_cast更安全。如:
【代碼5】
class A{
public:
A();
~A();
virtual void fun1();
void fun2();
};
class B:public A{
B();
~B();
virtual void fun3();
void fun4();
};
void f1(const A &a){ //引用類型的dynamic_cast
try{
const B &b = dynamic_cast<const B &>(a);
//使用a引用的B對象
}catch(std::bad_cast){
//處理類型轉換失敗的情況
}
}
void f2(){//指針類型的dynamic_cast
A *ap = new A();//使用基類對象的【指針】
if(B *bp = dynamic_cast<B *>(ap)){//動態轉換ap為指向B類型的指針;成功則繼續,不成功則為0
bp->fun4(); //執行某個‘派生類操作’並且’該操作‘不是‘虛函數’
//其他操作
}else{
//使用ap指向的A對象
}
delete ap; ap=NULL;
}
如果對無繼承關系或者沒有虛函數的對象指針進行轉換、基本類型指針轉換以及基類指針轉換為派生類指針,都不能通過編譯。
哪種情況下dynamic_cast和static_cast使用的情況一樣?
【代碼6】
class C{
//類內定義
};
class D:public A, public C{ //多重繼承
//類內定義
};
void f3(){ //單繼承情況下
//如果ap指向的即為B的對象,則使用dynamic_cast和static_cast效果一樣
A *ap = new B();
B *bp = dynamic_cast<B *>(ap); //A要有虛函數,否則使用dynamic_cast會產生編譯錯誤,
B *bp1 = static_cast<B *>(ap); //static_cast則沒有這個限制
delete ap; delete bp; delete bp1;
ap=bp=bp1=NULL; //防止野指針
//如果ap指向的不為B的對象,則用dynamic_cast返回NULL,能夠更早的禁止error的發生;
//如果用static_cast返回的不為NULL,但是運行時可能拋出異常
A *ap = new A();
B *bp = dynamic_cast<B *>(ap); //正確,但bp指向的為NULL
B *bp1 = static_cast<B *>(ap); //錯誤,bp1在運行時可能會拋出異常
delete ap; delete bp; delete bp1;
ap=bp=bp1=NULL; //防止野指針
}
void f4(){ //多繼承情況下
//如果ap指向的即為C的對象,則使用dynamic_cast和static_cast效果一樣,都可以轉換為C的指針
A *ap = new B();
C *cp = dynamic_cast<C *>(ap);
C *cp1 = static_cast<C *>(ap);
//若要將ap轉換為dp,則都可以,只是dynamic_cast可以一次轉換,static_cast需要一個過渡;dp的轉換相當於cp1和dp1,相當於dp2
D *dp = dynamic_cast<D *>(ap);
D *dp1 = static_cast<D *>(cp1);
D *dp2 = static_cast<D *>(static_cast<C *>(ap));
delete ap; delete cp; delete cp1; delete dp; delete dp1; delete dp2;
ap=cp=cp1=dp=dp1=dp2=NULL; //防止野指針
//如果ap指向的不為C的對象,則用dynamic_cast返回NULL,能夠更早的禁止error的發生;情況與f3中一樣!
}
dynamic_cast可用於進行交叉轉換:
【代碼7】
class E:public A{
//類內定義
};
void f5(){ //交叉轉換
//使用static_cast進行轉換是不被允許的,將在編譯時出錯;而使用 dynamic_cast的轉換則是允許的,結果是空指針。
B *bp = new B();
E *ep = dynamic_cast<E *>(bp);//正確,返回空指針
E *ep1 = static_cast<E *>(bp);//錯誤,編譯時出錯
//其他內容
}
dynamic_cast的討論:
在探究 dynamic_cast 的設計意圖之前,值得留意的是很多 dynamic_cast 的實現都相當慢。 例如,至少有一種通用的實現部分地基於對類名字進行字符串比較。如果你在一個位於四層深的單繼承體系中的對象上執行 dynamic_cast,在這樣一個實現下的每一個 dynamic_cast 都要付出相當於四次調用 strcmp 來比較類名字的成本。對於一個更深的或使用了多繼承的繼承體系,付出的代價會更加昂貴。
對 dynamic_cast 的需要通常發生在這種情況下:你要在一個你確信為派生類的對象上執行派生類的操作,但是你只能通過一個基類的指針或引用來操控這個對象。 有兩個一般的方法可以避免這個問題:
使用存儲著直接指向派生類對象的指針的容器,從而消除通過基類接口操控這個對象的需要。當然,這個方法不允許你在同一個容器中存儲所有可能的基類的派生類的指針。為了與不同的窗口類型一起工作,你可能需要多個類型安全(type-safe)的容器。
通過一個基類的接口操控所有可能的 Window 派生類,就是在基類中提供一個讓你做你想做的事情的虛函數。例如,盡管只有 SpecialWindows 能 blink,在基類中聲明這個函數,並提供一個什麼都不做的缺省實現或許是有意義的。
所以:避免強制轉型的隨意應用,特別是在性能敏感的代碼中應用 dynamic_casts,如果一個設計需要強制轉型,設法開發一個沒有強制轉型的侯選方案。 如果必須要強制轉型,設法將它隱藏在一個函數中。客戶可以用調用那個函數來代替在他們自己的代碼中加入強制轉型。
什麼情況下使用dynamic_cast代替虛函數?
當基類代碼不可知,需要在派生類裡面新增新成員函數,但是又無法取得基類的源代碼,不能在基類裡面通過加虛函數接口調用新成員函數,可以通過dynamic_cast強制轉換來獲得調用。如:
【代碼8】
class A{
public:
virtual void fun(){
cout<<"A::fun"<<endl;
}
//其他代碼未知,不能更改A的代碼
};
class B:public A{
public:
void fun(){
cout<<"B::fun"<<endl;
}
void get(){
cout<<"B::get"<<endl;
}
};
int main()
{
A *ap = new B;
ap->fun ();
(dynamic_cast<B*>(ap))->get (); //此時使用dynamic_cast獲取調用
if(B *o=dynamic_cast<B *>(ap))//此時使用dynamic_cast獲取調用
o->get();
cin.get();
return 0;
}
//輸出為:
B::fun
B::get
typeid運算符允許向表達式提問:“你的對象是什麼類型?”
typeid(e),其中e可以是任意表達式或類型的名字。操作結果是一個常量對象的引用。如果表達式為一個引用,則typeid返回該引用所引用對象的類型。如果e為數組或函數,則不會執行向指針的標准類型轉換,如數組,會返回數組類型。當運算對象不屬於類類型或者是一個不包含任何虛函數的類時,typeid運算符返回的是e的靜態類型。當e是定義了至少一個虛函數的類的左值時,typeid的結果直到運行時才能求得。
當typeid作用於指針p時,返回的是該指針的靜態編譯時類型;當作用於指針所指的對象(*p)時,要在運行時才能求得返回類型。同時,如果指針p所指的對象的類型不含有虛函數,則p可以為一個無效的指針。否則,指針所指的對象(*p)將在運行時求值,此時p必須為一個有效的指針,如p此時為一個空指針,則typeid(*p)會拋出std::bad_typeid異常。