C++強制型別轉換運算子(static_cast、reinterpret_cast、const_cast和dynamic_cast)

2020-07-16 10:04:28
將型別名作為強制型別轉換運算子的做法是C語言的老式做法,C++ 為保持相容而予以保留。

C++ 引入了四種功能不同的強制型別轉換運算子以進行強制型別轉換:static_cast、reinterpret_cast、const_cast 和 dynamic_cast。

強制型別轉換是有一定風險的,有的轉換並不一定安全,如把整型數值轉換成指標,把基礎類別指標轉換成派生類指標,把一種函數指標轉換成另一種函數指標,把常數指標轉換成非常數指標等。C++ 引入新的強制型別轉換機制,主要是為了克服C語言強制型別轉換的以下三個缺點。

1) 沒有從形式上體現轉換功能和風險的不同。

例如,將 int 強制轉換成 double 是沒有風險的,而將常數指標轉換成非常數指標,將基礎類別指標轉換成派生類指標都是高風險的,而且後兩者帶來的風險不同(即可能引發不同種類的錯誤),C語言的強制型別轉換形式對這些不同並不加以區分。

2) 將多型基礎類別指標轉換成派生類指標時不檢查安全性,即無法判斷轉換後的指標是否確實指向一個派生類物件。

3) 難以在程式中尋找到底什麼地方進行了強制型別轉換。

強制型別轉換是引發程式執行時錯誤的一個原因,因此在程式出錯時,可能就會想到是不是有哪些強制型別轉換出了問題。

如果採用C語言的老式做法,要在程式中找出所有進行了強制型別轉換的地方,顯然是很麻煩的,因為這些轉換沒有統一的格式。

而用 C++ 的方式,則只需要查詢_cast字串就可以了。甚至可以根據錯誤的型別,有針對性地專門查詢某一種強制型別轉換。例如,懷疑一個錯誤可能是由於使用了 reinterpret_cast 導致的,就可以只查詢reinterpret_cast字串。

C++ 強制型別轉換運算子的用法如下:

強制型別轉換運算子 <要轉換到的型別> (待轉換的表示式)

例如:

double d = static_cast <double> (3*5);  //將 3*5 的值轉換成實數

下面分別介紹四種強制型別轉換運算子。

static_cast

static_cast 用於進行比較“自然”和低風險的轉換,如整型和浮點型、字元型之間的互相轉換。另外,如果物件所屬的類過載了強制型別轉換運算子 T(如 T 是 int、int* 或其他型別名),則 static_cast 也能用來進行物件到 T 型別的轉換。

static_cast 不能用於在不同型別的指標之間互相轉換,也不能用於整型和指標之間的互相轉換,當然也不能用於不同型別的參照之間的轉換。因為這些屬於風險比較高的轉換。

static_cast 用法範例如下:
#include <iostream>
using namespace std;
class A
{
public:
    operator int() { return 1; }
    operator char*() { return NULL; }
};
int main()
{
    A a;
    int n;
    char* p = "New Dragon Inn";
    n = static_cast <int> (3.14);  // n 的值變為 3
    n = static_cast <int> (a);  //呼叫 a.operator int,n 的值變為 1
    p = static_cast <char*> (a);  //呼叫 a.operator char*,p 的值變為 NULL
    n = static_cast <int> (p);  //編譯錯誤,static_cast不能將指標轉換成整型
    p = static_cast <char*> (n);  //編譯錯誤,static_cast 不能將整型轉換成指標
    return 0;
}

reinterpret_cast

reinterpret_cast 用於進行各種不同型別的指標之間、不同型別的參照之間以及指標和能容納指標的整數型別之間的轉換。轉換時,執行的是逐個位元複製的操作。

這種轉換提供了很強的靈活性,但轉換的安全性只能由程式設計師的細心來保證了。例如,程式設計師執意要把一個 int* 指標、函數指標或其他型別的指標轉換成 string* 型別的指標也是可以的,至於以後用轉換後的指標呼叫 string 類的成員函數引發錯誤,程式設計師也只能自行承擔查詢錯誤的煩瑣工作:(C++ 標準不允許將函數指標轉換成物件指標,但有些編譯器,如 Visual Studio 2010,則支援這種轉換)。

reinterpret_cast 用法範例如下:
#include <iostream>
using namespace std;
class A
{
public:
    int i;
    int j;
    A(int n):i(n),j(n) { }
};
int main()
{
    A a(100);
    int &r = reinterpret_cast<int&>(a); //強行讓 r 參照 a
    r = 200;  //把 a.i 變成了 200
    cout << a.i << "," << a.j << endl;  // 輸出 200,100
    int n = 300;
    A *pa = reinterpret_cast<A*> ( & n); //強行讓 pa 指向 n
    pa->i = 400;  // n 變成 400
    pa->j = 500;  //此條語句不安全,很可能導致程式崩潰
    cout << n << endl;  // 輸出 400
    long long la = 0x12345678abcdLL;
    pa = reinterpret_cast<A*>(la); //la太長,只取低32位元0x5678abcd拷貝給pa
    unsigned int u = reinterpret_cast<unsigned int>(pa);//pa逐個位元拷貝到u
    cout << hex << u << endl;  //輸出 5678abcd
    typedef void (* PF1) (int);
    typedef int (* PF2) (int,char *);
    PF1 pf1;  PF2 pf2;
    pf2 = reinterpret_cast<PF2>(pf1); //兩個不同型別的函數指標之間可以互相轉換
}
程式的輸出結果是:
200, 100
400
5678abed

第 19 行的程式碼不安全,因為在編譯器看來,pa->j 的存放位置就是 n 後面的 4 個位元組。 本條語句會向這 4 個位元組中寫入 500。但這 4 個位元組不知道是用來存放什麼的,貿然向其中寫入可能會導致程式錯誤甚至崩潰。

上面程式中的各種轉換都沒有實際意義,只是為了演示 reinteipret_cast 的用法而已。在編寫駭客程式、病毒或反病毒程式時,也許會用到這樣怪異的轉換。

reinterpret_cast體現了 C++ 語言的設計思想:使用者可以做任何操作,但要為自己的行為負責。

const_cast

const_cast 運算子僅用於進行去除 const 屬性的轉換,它也是四個強制型別轉換運算子中唯一能夠去除 const 屬性的運算子。

將 const 參照轉換為同型別的非 const 參照,將 const 指標轉換為同型別的非 const 指標時可以使用 const_cast 運算子。例如:
const string s = "Inception";
string& p = const_cast <string&> (s);
string* ps = const_cast <string*> (&s);  // &s 的型別是 const string*

dynamic_cast

用 reinterpret_cast 可以將多型基礎類別(包含虛擬函式的基礎類別)的指標強制轉換為派生類的指標,但是這種轉換不檢查安全性,即不檢查轉換後的指標是否確實指向一個派生類物件。dynamic_cast專門用於將多型基礎類別的指標或參照強制轉換為派生類的指標或參照,而且能夠檢查轉換的安全性。對於不安全的指標轉換,轉換結果返回 NULL 指標。

dynamic_cast 是通過“執行時型別檢查”來保證安全性的。dynamic_cast 不能用於將非多型基礎類別的指標或參照強制轉換為派生類的指標或參照——這種轉換沒法保證安全性,只好用 reinterpret_cast 來完成。

dynamic_cast 範例程式如下:
#include <iostream>
#include <string>
using namespace std;
class Base
{  //有虛擬函式,因此是多型基礎類別
public:
    virtual ~Base() {}
};
class Derived : public Base { };
int main()
{
    Base b;
    Derived d;
    Derived* pd;
    pd = reinterpret_cast <Derived*> (&b);
    if (pd == NULL)
        //此處pd不會為 NULL。reinterpret_cast不檢查安全性,總是進行轉換
        cout << "unsafe reinterpret_cast" << endl; //不會執行
    pd = dynamic_cast <Derived*> (&b);
    if (pd == NULL)  //結果會是NULL,因為 &b 不指向派生類物件,此轉換不安全
        cout << "unsafe dynamic_cast1" << endl;  //會執行
    pd = dynamic_cast <Derived*> (&d);  //安全的轉換
    if (pd == NULL)  //此處 pd 不會為 NULL
        cout << "unsafe dynamic_cast2" << endl;  //不會執行
    return 0;
}
程式的輸出結果是:
unsafe dynamic_cast1

第 20 行,通過判斷 pd 的值是否為 NULL,就能知道第 19 行進行的轉換是否是安全的。第 23 行同理。

如果上面的程式中出現了下面的語句:

Derived & r = dynamic_cast <Derived &> (b);

那該如何判斷該轉換是否安全呢?不存在空參照,因此不能通過返回值來判斷轉換是否安全。C++ 的解決辦法是:dynamic_cast 在進行參照的強制轉換時,如果發現轉換不安全,就會拋出一個異常,通過處理異常,就能發現不安全的轉換。