歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

C++ 類型轉換及RTTI

一、C++的4中類型轉換
我們應該比較熟悉C的類型轉換即由圓括號和標識符組成,但是對於C的類型轉換有時候到不到我們的要求,比如去除const 的類型轉換,把一個指向基類的指針轉化成指向子類的指針等等。下面就介紹C++引進的四個新的類型轉換操作符,這四個操作符是:static_cast, const_cast, dynamic_cast, 和reinterpret_cast。

1,const_cast 除去對象的常屬性。轉換的是表達式而非自身. 形式:const_cast< type > ( object )

2,static_cast 用來進行非多態的任何轉換。拒絕了運行時的類型檢查。形式:static_cast< type > ( object )

3,dynamic_cast 這是唯一的運行時轉換符。可完成類族中的向下類型轉換——將父類的指針變為子類的指針。形式:dynamic_cast< type > (object)

4,reinterpret_cast 將一種數據從根本上變為另一種完全不兼容的類型。形式:reinterpret_cast< type > ( object )

下面分別仔細介紹:

const_cast< type > ( object )

該運算符用來修改類型的const或volatile屬性。除了const 或volatile修飾之外, type_id和expression的類型是一樣的。
A、常量指針被轉化成非常量的指針,並且仍然指向原來的對象;
B、常量引用被轉換成非常量的引用,並且仍然指向原來的對象;

下面是一些例子:

class Test
{
public:
    Test(int m_=0):m(m_){}
public:
    int m;
};
int main()
{
    const Test t1;
    //b1.m = 1; //compile error
    // 體現出轉換為指針類型
    Test *t2 = const_cast<Test*>(&t1);
    //左側為引用類型
    Test &t3 = const_cast<Test&>(t1);
    //對t2或t3的數據成員做改變,就是對t1的值在做改變
    t2->m = 2;    //ok
    printf("%d\n", t1.m);
    t3.m = 3;    //ok
    printf("%d\n", t1.m);
    return 0;
}

使用const_cast可以返回一個指向非常量的指針(或引用)指向原const 常量對象,可以通過轉換後的指針(或引用)對它的成員進行改變。

 

可見只有使用const_cast才能將 const性質轉換掉。在這種情況下,試圖使用其他三種形式的強制轉換都會導致編譯時的錯誤。除此之外,

用 const_cast 符來執行其他任何類型轉換,都會引起編譯錯誤。

static_cast< type > ( object )

該運算符把expression轉換為type-id類型,但沒有運行時類型檢查來保證轉換的安全性。它主要有如下幾種用法:

①用於類層次結構中基類(父類)和派生類(子類)之間指針或引用的轉換。進行上行轉換(把派生類的指針或引用轉換成基類表示)是安全的;
    進行下行轉換(把基類指針或引用轉換成派生類表示)時,由於沒有動態類型檢查,所以是不安全的。

②用於基本數據類型之間的轉換,如把int轉換成char,把int轉換成enum。這種轉換的安全性也要開發人員來保證。

③把空指針轉換成目標類型的空指針。

④把任何類型的表達式轉換成void類型。
注意:static_cast不能轉換掉expression的const、volatile、或者__unaligned屬性。

編譯器隱式執行的任何類型轉換都可以由static_cast顯式完成。如:

float pi=3.1415;
int a=static_cast<int>(pi);

dynamic_cast< type > (object)

該運算符把object轉換成type類型的對象。type必須是類的指針、類的引用或者void*;

dynamic_cast運算符可以在執行期決定真正的類型。如果downcast是安全的(基類指針或者引用指向一個派生類對象,把基類指針或引用轉換為派生類指針或引用)

這個運算符會傳回適當轉型過的指針。如果downcast不安全(基類指針或者引用沒有指向一個派生類對象),這個運算符會傳回空指針。

dynamic_cast主要用於類層次間的上行轉換和下行轉換,還可以用於類之間的交叉轉換。

在類層次間進行上行轉換時,dynamic_cast和static_cast的效果是一樣的;
在進行下行轉換時,dynamic_cast具有類型檢查的功能,比static_cast更安全。

例子:

class Base
{
public:
    virtual void print()
    {
        printf("I am Base. virtual print.\n");
    }
};

class Der:public Base
{
public:
    void print()
    {
        printf("I am Derive. virtual print.\n");
    }
    void print_non_vir()
    {
        printf("I am Derive. non virtual print.\n");
    }
};
int main()
{
    Base b;
    Der d;
    Base *pbb=&b;
    Base *pbd=&d;
    pbd->print();
//    pbd->print_non_vir(); //error
    dynamic_cast<Der*>(pbd)->print_non_vir();
 //  dynamic_cast<Der*>(pbb)->n=2;//error pbb所指對象為Base
    static_cast<Der*>(pbd)->print_non_vir();
    return 0;
}

注意:Base要有虛函數,否則會編譯出錯;static_cast則沒有這個限制。

dynamic_cast轉換符只能用於指針或者引用。dynamic_cast轉換符只能用於含有虛函數的類。dynamic_cast轉換操作符在執行類型轉換時首先將檢查能否成功轉換,

如果能成功轉換則轉換之,如果轉換失敗,如果是指針則反回一個0值,如果是轉換的是引用,則拋出一個bad_cast異常,所以在使用dynamic_cast轉換之間應使用

if語句對其轉換成功與否進行測試,比如

if(dynamic_cast<D*>(pb))
{
    ...
}
else
{
    ...
}

dynamic_cast能將基類類型的指針或引用安全地轉換為派生類型的指針或引用, 它是怎麼實現的呢?

它是通過運行時類型識別(RTTI),程序能夠使用基類的指針或引用來檢索這些指針或引用所指對象的實際派生類型,在第2部分會介紹。

reinterpret_cast< type > ( object )

type 必須是一個指針、引用、算術類型、函數指針或者成員指針。它可以把一個指針轉換成一個整數,也可以把一個整數轉換成一個指針

(先把一個指針轉換成一個整數,在把該整數轉換成原類型的指針,還可以得到原先的指針值)。

操作符修改了操作數類型,但僅僅是重新解釋了給出的對象的比特模型而沒有進行二進制轉換。

C++中static_cast和reinterpret_cast的區別:

C++primer裡寫了編譯器隱式執行任何類型轉換都可由static_cast顯示完成;reinterpret_cast通常為操作數的位模式提供較低層的重新解釋。

C++中的reinterpret_cast主要是將數據從一種類型的轉換為另一種類型。所謂“通常為操作數的位模式提供較低層的重新解釋”也就是說將數據以二進制存在形式的重新解釋。比如:

int i;
char *p = "This is a test.";
i = reinterpret_cast<int>(p);

此時結果,i與p的值是完全相同的。reinterpret_cast的作用是說將指針p的值以二進制(位模式)的方式被解釋為整型,並賦給i.

二、RTTI
RTTI(Run-Time Type Information,通過運行時類型信息)程序能夠使用基類的指針或引用來檢查這些指針或引用所指的對象的實際派生類型。


RTTI提供了以下兩個非常有用的操作符:
(1)typeid操作符,返回指針和引用所指的實際類型。
(2)dynamic_cast操作符,將基類類型的指針或引用安全地轉換為派生類型的指針或引用。

typeid 操作符的結果是名為type_info的標准庫類型的對象引用,類type_info在庫頭文件 typeinfo中定義。typeid 操作符可以與任何類型的表達式一起使用。

內置類型的表達式以及常量都可以用作 typeid 操作符的操作數。如果操作數不是類類型或者是沒有虛函數的類,則 typeid 操作符指出操作數的靜態類型;

如果操作數是定義了至少一個虛函數的類類型,則在運行時計算類型。

Base *bp;

    Derived *dp;

    // compare type at run time of twoobjects

    if (typeid(*bp) ==typeid(*dp)) {

        // bp and dp point to objects of thesame type

    }

    // test whether run time type is aspecific type

    if (typeid(*bp) ==typeid(Derived)) {

        // bp actually points to aDerived

    }//特別注意,只有當 typeid 的操作數是帶虛函數的類類型的對象的時候,才返回動態類型信息.

type_info的復制構造函數以及賦值操作符都定義為 private,所以不能定義或復制 type_info類型的對象。程序中創建 type_info 對象的唯一方法是使用 typeid 操作符。

type_info類確切定義隨編譯器而變化,但是標准保證至少實現operator==,operator=!,type_info::name()及type_info::before()。
運行時獲知變量類型名稱,可以使用 typeid(變量).name ,如:printf("%s\n", typeid(int).name()); 其輸出不一定是 int ,我這裡輸出是 i。

對dynamic_cast 它究竟是怎麼實現的呢?

最簡單的方案就是將信息保存在vtable裡,它會占用一個vtalbe表的項目,具體做法可以參考大作《Inside C++ Model》RTTI 的info 是如何和對象之間的關聯。

舉例

class Point
{
public:
  Point( float xval );
  virtual ~Point();
  float x() const;
  static int PointCount();
protected:
  virtual ostream& print( ostream &os ) const;
  float _x;
  static int _point_count;

};

其對應的內存模型:

通過上圖可知 RTTI  info 存在於虛表的第一項。可以回答 “dynamic_cast轉換符只能用於含有虛函數的類”,因為RTTI 依賴於虛表,所以用dynamic_cast 對應的類一定要有虛函數。

Copyright © Linux教程網 All Rights Reserved