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關系,指針和引用進行向上轉換是安全的,向下轉換就是不安全的。

dynamic_cast的用法是:

dynamic_cast<Type*>(ptr)dynamic_cast<Type&>(ref)

可以看到的是,dynamic_cast是一種類型轉換符,它支持指針間的轉換,他將ptr的類型轉換成Type*,如果這種轉換是安全的,那會返回轉換後的指針;如果不安全那就會返回空指針。對於引用的話,他將ref的類型轉換成Type&,如果這種轉換是安全的,那就返回轉換後的引用;如果不安全那就會引發bad_cast異常。

總之,dynamic_cast類型轉換運算符比C風格的類型轉換要安全的多,而且它能夠判斷轉換是否安全。

//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

上面這個例子說明瞭重要的一點:盡可能使用虛函數,而隻在必要時使用RTTI。

如果將dynamic_cast用於引用,當請求不安全的時候,會引發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種參數:類名和對象。typeid是真正回答瞭"指針指向的是哪類對象"。

(實際上,typeid也可以用於其他類型,例如結構體,函數指針,各種類型,隻要sizeof能接受的,typeid都能接受)

用法:

typeid(Magnificent)==typeid(*pg)

如果pg是一個空指針,則會引發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!

註意,你可能發現瞭typeid遠比dynamic_cast優秀的多,typeid會直接告訴你類型是什麼,而dynamic_cast隻告訴你是否可以類型轉換。但是typeid的效率比dynamic_cast低很多,所以,請優先考慮dynamic_cast和虛函數,如果不行才使用typeid

2. cast運算符

C語言中的類型轉換符太過隨意,C++創始人認為,我們應該使用嚴格的類型轉換,則C++提供瞭4個類型轉換運算符:

  • dynamic_cast
  • const_cast
  • static_cast
  • reinterpret_cast

dynamic_cast運算符已經介紹過瞭,它的用途是,使得類層次結構中進行向上轉換,而不允許其他轉換。

const_cast運算符,用於除去或增加 類型的constvolatile屬性。

它的語法是:

const_cast<type-name>(expression)

如果類型的其他方面也被修改,則上述類型轉換就會出錯,也就是說,除瞭cv限定符可以不同外,type_nameexpression的類型必須相同。

提供該運算符的目的是:有時候我們需要一個值:它在大多數情況下是常量,而有時候我們需要更改它。

看一個有趣的例子:

//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,這是我們預期的結果。但是如果我把volatile關鍵詞去掉,那麼a的值還是100。其實是編譯器在這裡玩瞭個小聰明,const int a=100,編譯器認為a就不會發生改變瞭,所以就把a放在瞭寄存器中,因為訪問寄存器要比訪問內存單元快的多,每次都從寄存器中取數據,但是後來a在內存中發生變化後,寄存器中的數據沒有發生變化,所以打印的是寄存器中的數據。解決這一問題,就使用volatile關鍵詞,那麼對a的存取都是直接對內存做操作的。

static_cast運算符

它的語法是:

static_cast<type-name>(expression)

僅當type-name可被隱式轉換成expression所屬的類型或者expression可以隱式轉換成type-name類型時,上述轉換才合法,否則出現bad_cast異常。

例如,枚舉值可以隱式轉換成整型,那麼整型就可以通過static_cast轉換成枚舉型。

總之,static_cast隻允許"相似"的類型間的轉換,而不允許"差異很大"的類間的轉換。

reinterpret_cast運算符:重新詮釋類型,進行瘋狂的類型轉換

它的語法時:

reinterpret_cast<type-name>(expression)

它可以進行類型間"不可思議"的互換,但是它不允許刪除const,也不允許進行喪失數據的轉換

例如下面這兩個例子:

#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轉換符適用於底層編程技術,是不可移植的,因為不同系統可能使用不同大小,不同順序來存儲同一類型的數據。

總之,C語言類型轉換比reinterpret_cast還要"瘋狂",非常不安全,所以推薦使用cast運算符來實現類型轉換。

到此這篇關於C++超詳細講解RTTI和cast運算符的使用的文章就介紹到這瞭,更多相關C++ RTTI和cast內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: