C++超詳細講解RTTI和cast運算符的使用
1. RTTI
RTTI是運行階段類型識別(Running Type Identificarion)的簡稱。
如何知道指針指向的是哪種對象?
這是個很常見的問題,由于我們允許使用基類指針指向派生類,所以基類指針指向的對象可能是基類對象,也可能是派生類對象。但是我們需要知道對象種類,因為我們需要使用正確的類方法。
RTTI能解決上述問題。
1.1 dynamic_cast運算符
dynamic_cast是最常用的RTTI組件,它不能回答"指針指向的是哪類對象",但是它能回答"是否可以安全的將對象的地址賦給特定類型指針"。
什么是安全?
例如:A是基類,B是A的派生類,C是B的派生類。那么將BC對象的地址賦給A指針是安全的,而反向就是不安全的。因為公有繼承滿足is-a關(guān)系,指針和引用進行向上轉(zhuǎn)換是安全的,向下轉(zhuǎn)換就是不安全的。
dynamic_cast的用法是:
dynamic_cast<Type*>(ptr)或dynamic_cast<Type&>(ref)
可以看到的是,dynamic_cast是一種類型轉(zhuǎn)換符,它支持指針間的轉(zhuǎn)換,他將ptr的類型轉(zhuǎn)換成Type*,如果這種轉(zhuǎn)換是安全的,那會返回轉(zhuǎn)換后的指針;如果不安全那就會返回空指針。對于引用的話,他將ref的類型轉(zhuǎn)換成Type&,如果這種轉(zhuǎn)換是安全的,那就返回轉(zhuǎn)換后的引用;如果不安全那就會引發(fā)bad_cast異常。
總之,dynamic_cast類型轉(zhuǎn)換運算符比C風(fēng)格的類型轉(zhuǎn)換要安全的多,而且它能夠判斷轉(zhuǎn)換是否安全。
//RTTI1.cpp
#include<iostream>
#include<cstdlib>
#include<ctime>
using namespace std;
class Grand
{
protected:
int hold;
public:
Grand(int h=0):hold(h){};
virtual void Speak() const {cout<<"I am Grand class!\n";}
};
class Superb:public Grand
{
public:
Superb(int h=0):Grand(h){}
virtual void Speak() const {cout<<"I am a superb class!\n";}
virtual void Say() const {cout<<"the value: "<<Grand::hold<<endl;}
};
class Magnificent:public Superb
{
private:
char ch;
public:
Magnificent(int h=0,char c='A'):Superb(h),ch(c){}
virtual void Speak() const{cout<<"I am a Magnificent class!\n";}
virtual void Say() const{cout<<"the character: "<<ch<<endl<<"the value: "<<Grand::hold<<endl;}
};
Grand *GetOne();
int main()
{
srand(time(0));
Grand *pg;
Superb *ps;
for(int i=0;i<5;i++)
{
pg=GetOne();
pg->Speak();
if(ps=dynamic_cast<Superb*>(pg))
{
ps->Say();
}
}
delete pg;
return 0;
}
Grand * GetOne()
{
Grand *p;
switch (rand()%3)
{
case 0:
p=new Grand(rand()%100);
break;
case 1:
p=new Superb(rand()%100);
break;
case 2:
p=new Magnificent(rand()%100,'A'+rand()%26);
break;
}
return p;
}I am a Magnificent class!
the character: I
the value: 74
I am a superb class!
the value: 50
I am Grand class!
I am Grand class!
I am a Magnificent class!
the character: V
the value: 99
上面這個例子說明了重要的一點:盡可能使用虛函數(shù),而只在必要時使用RTTI。
如果將dynamic_cast用于引用,當請求不安全的時候,會引發(fā)bad_cast異常,這種異常時從exception類派生而來的,它在頭文件typeinfo中定義。
則我們可以使用如下方式處理異常:
#include<typeinfo>
...
try{
Superb & rs= dynamic_cast<Superb &>(rg);
....
}
catch(bad_cast &)
{
...
};
1.2 typeid運算符
type_info類是在頭文件typeinfo中定義的一個類。type_info類重載了==和!=運算符。
typeid是一個運算符,它返回一個對type_info的引用,它可以接受2種參數(shù):類名和對象。typeid是真正回答了"指針指向的是哪類對象"。
(實際上,typeid也可以用于其他類型,例如結(jié)構(gòu)體,函數(shù)指針,各種類型,只要sizeof能接受的,typeid都能接受)
用法:
typeid(Magnificent)==typeid(*pg)
如果pg是一個空指針,則會引發(fā)bad_typeid異常。
type_info類中包含一個name()接口,該接口返回字符串,一般情況下是類的名稱。
cout<<typeid(*pg).name()<<".\n"
例子:
//RTTI2.cpp
#include<iostream>
#include<cstdlib>
#include<ctime>
#include<typeinfo>
using namespace std;
class Grand
{
protected:
int hold;
public:
Grand(int h=0):hold(h){};
virtual void Speak() const {cout<<"I am Grand class!\n";}
};
class Superb:public Grand
{
public:
Superb(int h=0):Grand(h){}
virtual void Speak() const {cout<<"I am a superb class!\n";}
virtual void Say() const {cout<<"the value: "<<Grand::hold<<endl;}
};
class Magnificent:public Superb
{
private:
char ch;
public:
Magnificent(int h=0,char c='A'):Superb(h),ch(c){}
virtual void Speak() const{cout<<"I am a Magnificent class!\n";}
virtual void Say() const{cout<<"the character: "<<ch<<endl<<"the value: "<<Grand::hold<<endl;}
};
Grand *GetOne();
int main()
{
srand(time(0));
Grand *pg;
Superb *ps;
for(int i=0;i<5;i++)
{
pg=GetOne();
cout<<"Now processing type "<<typeid(*pg).name()<<".\n";
pg->Speak();
if(ps=dynamic_cast<Superb*>(pg))
{
ps->Say();
}
if(typeid(Magnificent)==typeid(*pg))
{
cout<<"Yes,you are really magnificent.\n";
}
}
delete pg;
return 0;
}
Grand * GetOne()
{
Grand *p;
switch (rand()%3)
{
case 0:
p=new Grand(rand()%100);
break;
case 1:
p=new Superb(rand()%100);
break;
case 2:
p=new Magnificent(rand()%100,'A'+rand()%26);
break;
}
return p;
}Now processing type 6Superb.
I am a superb class!
the value: 64
Now processing type 11Magnificent.
I am a Magnificent class!
the character: Y
the value: 30
Yes,you are really magnificent.
Now processing type 5Grand.
I am Grand class!
Now processing type 11Magnificent.
I am a Magnificent class!
the character: C
the value: 3
Yes,you are really magnificent.
Now processing type 5Grand.
I am Grand class!
注意,你可能發(fā)現(xiàn)了typeid遠比dynamic_cast優(yōu)秀的多,typeid會直接告訴你類型是什么,而dynamic_cast只告訴你是否可以類型轉(zhuǎn)換。但是typeid的效率比dynamic_cast低很多,所以,請優(yōu)先考慮dynamic_cast和虛函數(shù),如果不行才使用typeid。
2. cast運算符
C語言中的類型轉(zhuǎn)換符太過隨意,C++創(chuàng)始人認為,我們應(yīng)該使用嚴格的類型轉(zhuǎn)換,則C++提供了4個類型轉(zhuǎn)換運算符:
dynamic_castconst_caststatic_castreinterpret_cast
dynamic_cast運算符已經(jīng)介紹過了,它的用途是,使得類層次結(jié)構(gòu)中進行向上轉(zhuǎn)換,而不允許其他轉(zhuǎn)換。
const_cast運算符,用于除去或增加 類型的const或volatile屬性。
它的語法是:
const_cast<type-name>(expression)
如果類型的其他方面也被修改,則上述類型轉(zhuǎn)換就會出錯,也就是說,除了cv限定符可以不同外,type_name和expression的類型必須相同。
提供該運算符的目的是:有時候我們需要一個值:它在大多數(shù)情況下是常量,而有時候我們需要更改它。
看一個有趣的例子:
//cast運算符1.cpp
//cast運算符1.cpp
#include <iostream>
int main()
{
using std::cout;
using std::endl;
volatile const int a=100;
volatile const int & ra=a;//無法通過ra修改a
int &change_a=const_cast<int &>(ra);//可以通過change_a修改a;
change_a=255;
cout<<a<<endl;
}
上面最后這個a常量被修改成255,這是我們預(yù)期的結(jié)果。但是如果我把volatile關(guān)鍵詞去掉,那么a的值還是100。其實是編譯器在這里玩了個小聰明,const int a=100,編譯器認為a就不會發(fā)生改變了,所以就把a放在了寄存器中,因為訪問寄存器要比訪問內(nèi)存單元快的多,每次都從寄存器中取數(shù)據(jù),但是后來a在內(nèi)存中發(fā)生變化后,寄存器中的數(shù)據(jù)沒有發(fā)生變化,所以打印的是寄存器中的數(shù)據(jù)。解決這一問題,就使用volatile關(guān)鍵詞,那么對a的存取都是直接對內(nèi)存做操作的。
static_cast運算符
它的語法是:
static_cast<type-name>(expression)
僅當type-name可被隱式轉(zhuǎn)換成expression所屬的類型或者expression可以隱式轉(zhuǎn)換成type-name類型時,上述轉(zhuǎn)換才合法,否則出現(xiàn)bad_cast異常。
例如,枚舉值可以隱式轉(zhuǎn)換成整型,那么整型就可以通過static_cast轉(zhuǎn)換成枚舉型。
總之,static_cast只允許"相似"的類型間的轉(zhuǎn)換,而不允許"差異很大"的類間的轉(zhuǎn)換。
reinterpret_cast運算符:重新詮釋類型,進行瘋狂的類型轉(zhuǎn)換
它的語法時:
reinterpret_cast<type-name>(expression)
它可以進行類型間"不可思議"的互換,但是它不允許刪除const,也不允許進行喪失數(shù)據(jù)的轉(zhuǎn)換
例如下面這兩個例子:
#include<iostream>
int main()
{
using namespace std;
struct dat {short a; short b;};
long value =0xA224B118;
dat *pd=reinterpret_cast<dat*>(&value);
cout<<hex<<pd->a;
}
#include<iostream>
void fun()
{
std::cout<<"hello world!\n";
}
int main()
{
using namespace std;
void (*foo)();
foo=fun;
int* p=reinterpret_cast<int*>(foo);
}
通常,reinterpret_cast轉(zhuǎn)換符適用于底層編程技術(shù),是不可移植的,因為不同系統(tǒng)可能使用不同大小,不同順序來存儲同一類型的數(shù)據(jù)。
總之,C語言類型轉(zhuǎn)換比reinterpret_cast還要"瘋狂",非常不安全,所以推薦使用cast運算符來實現(xiàn)類型轉(zhuǎn)換。
到此這篇關(guān)于C++超詳細講解RTTI和cast運算符的使用的文章就介紹到這了,更多相關(guān)C++ RTTI和cast內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
c++ lambda捕獲this 導(dǎo)致多線程下類釋放后還在使用的錯誤問題
Lambda表達式是現(xiàn)代C++的一個語法糖,挺好用的。但是如果使用不當,會導(dǎo)致內(nèi)存泄露或潛在的崩潰問題,這里總結(jié)下c++ lambda捕獲this 導(dǎo)致多線程下類釋放后還在使用的錯誤問題,感興趣的朋友一起看看吧2023-02-02
opencv配置的完整步驟(win10+VS2015+OpenCV3.1.0)
OpenCV是計算機視覺中經(jīng)典的專用庫,其支持多語言、跨平臺,功能強大,這篇文章主要給大家介紹了關(guān)于opencv配置(win10+VS2015+OpenCV3.1.0)的相關(guān)資料,需要的朋友可以參考下2021-06-06
Pipes實現(xiàn)LeetCode(195.第十行)
這篇文章主要介紹了Pipes實現(xiàn)LeetCode(195.第十行),本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-08-08

