1. 程式人生 > >C++強制型別轉換:static_cast、dynamic_cast、const_cast、reinterpret_cast

C++強制型別轉換:static_cast、dynamic_cast、const_cast、reinterpret_cast

1. c強制轉換與c++強制轉換

 c語言強制型別轉換主要用於基礎的資料型別間的轉換,語法為:

(type-id)expression//轉換格式1

type-id(expression)//轉換格式2

c++除了能使用c語言的強制型別轉換外,還新增了四種強制型別轉換:static_cast、dynamic_cast、const_cast、reinterpret_cast,主要運用於繼承關係類間的強制轉化,語法為:

static_cast<new_type>      (expression)
dynamic_cast<new_type>     (expression) 
const_cast<new_type>       (expression) 
reinterpret_cast<new_type> (expression)

備註:new_type為目標資料型別,expression為原始資料型別變數或者表示式。

《Effective C++》中將c語言強制型別轉換稱為舊式轉型,c++強制型別轉換稱為新式轉型

2. static_cast、dynamic_cast、const_cast、reinterpret_cast

  •  static_cast

static_cast相當於傳統的C語言裡的強制轉換,該運算子把expression轉換為new_type型別,用來強迫隱式轉換,例如non-const物件轉為const物件,編譯時檢查,用於非多型的轉換,可以轉換指標及其他,但沒有執行時型別檢查來保證轉換的安全性

。它主要有如下幾種用法:

①用於類層次結構中基類(父類)和派生類(子類)之間指標或引用的轉換。

進行上行轉換(把派生類的指標或引用轉換成基類表示)是安全的;

進行下行轉換(把基類指標或引用轉換成派生類表示)時,由於沒有動態型別檢查,所以是不安全的。

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

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

④把任何型別的表示式轉換成void型別。

注意:static_cast不能轉換掉expression的const、volatile、或者__unaligned屬性。

基本型別資料轉換舉例如下:

char a = 'a';
int b = static_cast<char>(a);//正確,將char型資料轉換成int型資料

double *c = new double;
void *d = static_cast<void*>(c);//正確,將double指標轉換成void指標

int e = 10;
const int f = static_cast<const int>(e);//正確,將int型資料轉換成const int型資料

const int g = 20;
int *h = static_cast<int*>(&g);//編譯錯誤,static_cast不能轉換掉g的const屬性

類上行和下行轉換:

//
// Created by rbcheng on 10/20/18.
//
#include <iostream>
#include <typeinfo>

class Father {
public:
    virtual std::string name() {
        return "Father";
    }
};

class Son: public Father {
public:
    std::string name() override {
        return "Son";
    }
};

class GrandSon: public Son {
public:
    std::string name() override {
        return "GrandSon";
    }
};

class Derived {

};

int main() {

    Father* father = new Son();
    GrandSon* grand_son = static_cast<GrandSon*>(father);  // 下行轉換不安全


    std::cout << father->name() << std::endl;              // 輸出結果為Son
    std::cout << grand_son->name() << std::endl;           // 輸出結果本應該為GrandSon, 但輸出結果為Son
    
    GrandSon* grand_son1 = new GrandSon();
    Father* father1 = static_cast<Father*>(grand_son1);    // 上行轉換安全
    std::cout << father1->name() << std::endl;             // 輸出結果為GrandSon

    delete grand_son1;
    delete father;

    return 0;
}
輸出結果如下:
Son
Son
GrandSon

Process finished with exit code 0
  • dynamic_cast

dynamic_cast<type*>(e)
dynamic_cast<type&>(e)
dynamic_cast<type&&>(e)

  type必須是一個類型別,在第一種形式中,type必須是一個有效的指標,在第二種形式中,type必須是一個左值,在第三種形式中,type必須是一個右值。在上面所有形式中,e的型別必須符合以下三個條件中的任何一個:e的型別是是目標型別type的公有派生類、e的型別是目標type的共有基類或者e的型別就是目標type的的型別。如果一條dynamic_cast語句的轉換目標是指標型別並且失敗了,則結果為0。如果轉換目標是引用型別並且失敗了,則dynamic_cast運算子將丟擲一個std::bad_cast異常(該異常定義在typeinfo標準庫標頭檔案中)。e也可以是一個空指標,結果是所需型別的空指標。

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

在類層次間進行上行轉換時,dynamic_cast和static_cast的效果是一樣的;

在進行下行轉換時,dynamic_cast具有型別檢查的功能,比static_cast更安全。dynamic_cast是唯一無法由舊式語法執行的動作,也是唯一可能耗費重大執行成本的轉型動作。

(1)指標型別

舉例,Base為包含至少一個虛擬函式的基類,Derived是Base的共有派生類,如果有一個指向Base的指標bp,我們可以在執行時將它轉換成指向Derived的指標,程式碼如下:

// 如果bp不是Derived*型別,型別轉換後dp == nullptr
if(Derived *dp = dynamic_cast<Derived *>(bp)){
  //使用dp指向的Derived物件  
}
else{
  //使用bp指向的Base物件  
}

值得注意的是,在上述程式碼中,if語句中定義了dp,這樣做的好處是可以在一個操作中同時完成型別轉換和條件檢查兩項任務。

(2)引用型別

因為不存在所謂空引用,所以引用型別的dynamic_cast轉換與指標型別不同,在引用轉換失敗時,會丟擲std::bad_cast異常,該異常定義在標頭檔案typeinfo中。

void f(const Base &b){
 try{
   const Derived &d = dynamic_cast<const Base &>(b);  
   //使用b引用的Derived物件
 }
 catch(std::bad_cast){
   //處理型別轉換失敗的情況
 }
}
  • const_cast

const_cast,用於修改型別的const或volatile屬性。 

該運算子用來修改型別的const(唯一有此能力的C++-style轉型操作符)或volatile屬性。除了const 或volatile修飾之外, new_type和expression的型別是一樣的。

①常量指標被轉化成非常量的指標,並且仍然指向原來的物件;

②常量引用被轉換成非常量的引用,並且仍然指向原來的物件;

③const_cast一般用於修改底指標。如const char *p形式。

舉例轉換如下:

const int g = 20;
int *h = const_cast<int*>(&g);//去掉const常量const屬性

const int g = 20;
int &h = const_cast<int &>(g);//去掉const引用const屬性

 const char *g = "hello";
char *h = const_cast<char *>(g);//去掉const指標const屬性
  • reinterpret_cast

new_type必須是一個指標、引用、算術型別、函式指標或者成員指標。它可以把一個指標轉換成一個整數,也可以把一個整數轉換成一個指標(先把一個指標轉換成一個整數,再把該整數轉換成原型別的指標,還可以得到原先的指標值)。

reinterpret_cast意圖執行低階轉型,實際動作(及結果)可能取決於編輯器,這也就表示它不可移植

  舉一個錯誤使用reintepret_cast例子,將整數型別轉換成函式指標後,vc++在執行過程中會報"...中的 0xxxxxxxxx 處有未經處理的異常: 0xC0000005: Access violation"錯誤:

#include <iostream>
using namespace std;
int output(int p){
    cout << p <<endl;
  return 0;
}

typedef int (*test_func)(int );//定義函式指標test_func
int main(){
    int p = 10;
    test_func fun1 = output;
    fun1(p);//正確
    test_func fun2 = reinterpret_cast<test_func>(&p);
    fun2(p);//...處有未經處理的異常: 0xC0000005: Access violation
    return 0;
}

IBM的C++指南、C++之父Bjarne Stroustrup的FAQ網頁MSDN的Visual C++也都指出:錯誤的使用reinterpret_cast很容易導致程式的不安全,只有將轉換後的型別值轉換回到其原始型別,這樣才是正確使用reinterpret_cast方式。

  MSDN中也提到了,實際中可將reinterpret_cast應用到雜湊函式中,如下(64位系統中需將unsigned int修改為unsigned long):

// expre_reinterpret_cast_Operator.cpp
// compile with: /EHsc
#include <iostream>

// Returns a hash code based on an address
unsigned short Hash( void *p ) {
   unsigned int val = reinterpret_cast<unsigned int>( p );
   return ( unsigned short )( val ^ (val >> 16));
}

using namespace std;
int main() {
   int a[20];
   for ( int i = 0; i < 20; i++ )
      cout << Hash( a + i ) << endl;
}

  另外,static_cast和reinterpret_cast的區別主要在於多重繼承,比如

class A {
    public:
    int m_a;
};
 
class B {
    public:
    int m_b;
};
 
class C : public A, public B {};

  那麼對於以下程式碼:

C c;
printf("%p, %p, %p", &c, reinterpret_cast<B*>(&c), static_cast <B*>(&c));

       輸出結果如下:

0x7ffceac55268, 0x7ffceac55268, 0x7ffceac5526c
Process finished with exit code 0

  前兩個的輸出值是相同的,最後一個則會在原基礎上偏移4個位元組,這是因為static_cast計算了父子類指標轉換的偏移量,並將之轉換到正確的地址(c裡面有m_a,m_b,轉換為B*指標後指到m_b處),而reinterpret_cast卻不會做這一層轉換。

 因此, 你需要謹慎使用 reinterpret_cast。

3. c++強制轉換注意事項

  • 新式轉換較舊式轉換更受歡迎。原因有二,一是新式轉型較易辨別,能簡化“找出型別系統在哪個地方被破壞”的過程;二是各轉型動作的目標愈窄化,編譯器愈能診斷出錯誤的運用。
  • 儘量少使用轉型操作,尤其是dynamic_cast,耗時較高,會導致效能的下降,儘量使用其他方法替代。