C/C++是有類型語言,在表達式計算,表達式賦值和函數調用時都會發生各種類型轉換。很多場合下,爲了使上述類型轉換合法、有效且滿足特定需求,我們需要對錶達式執行顯式的類型轉換。在這樣的場合,如何選擇合適的轉換函數,是我們不得不面對的一個問題。本篇我們總結整理了cpp文檔,對cpp中四種cast接口:const_cast
、static_cast
、dynamic_cast
、reinterpret_cast
進行了簡要的總結。
const_cast
const_cast<new_type>(exp: type)
:在有不同const,volatile限定符的類型間轉換。
-
功能:移除表達式的常量性和易變性。 例如調用某個形參爲const T &的函數時,希望以(non-const)T類型作爲參數。
-
分類:
- new_type和type爲指向同一類型的指針,但是擁有不同的const,volatile限定符。
- 空指針值可轉換成new_type的空指針值。
-
常用例子:
void Double(int & a) { cout << a << endl; a = a * 2; cout << a << endl; } int main(void) { const int tmp = 1; Double(const_cast<int &>(tmp)); }
static_cast
static_cast<new_type>(exp: type)
: 用隱式轉換和用戶定義轉換的組合在類型間轉換。
-
功能:顯式、靜態地執行隱式類型轉換和用戶定義的轉換,不進行運行時檢查。
-
分類:
- 隱式類型轉換和大部分隱式轉換的逆轉換。
- 初始化轉換: 存在初始化函數 new_type(type)。
- 子類到非虛父類的靜態轉換。
- 左值到右值,數組到指針,函數到指針。
- 棄值表達式:new_type爲void。
- void * 到任何類型。
-
常用例子:
int n = static_cast<int>(3.14); //float 轉 int std::vector<int> v = static_cast<std::vector<int>>(10); // 初始化轉換 std::vector<int> v2 = static_cast<std::vector<int>&&>(v); //左值到右值 static_cast<void>(v2.size()); //棄值表達式 void* voidp = &n; std::vector<int>* p = static_cast<std::vector<int>*>(voidp); // void*轉其他
dynamic_cast
dynamic_cast<new_type >(exp:type)
:沿繼承層級向上、向下及側向,安全地轉換到其他類的指針和引用。
-
功能:在進行繼承層級上的轉換時,執行運行時檢查,保證轉型的安全性。
-
分類:
- 向下轉型: type是new_type的公有基類,且從原對象中只能推導出一個new_type對象。
- 側向轉型: type和new_type相互無繼承關係,但type和new_type都是原對象的公有基類。
- 向上轉型: new_type是type的公有基類,可直接使用隱式類型轉換或者static_cast。
- badcast: 均不滿足,指向new_type的空指針。
-
常用例子:
// |--A--| // 虛基類 V-->| |-->D 繼承關係表明:D同時繼承了A、B // |--B--| A& a = d; // 向上轉型,可以用 dynamic_cast,但不必須 D& new_d = dynamic_cast<D&>(a); // 向下轉型 B& new_b = dynamic_cast<B&>(a); // 側向轉型
reinterpret_cast
reinterpret_cast<new_type>(exp: type)
:通過重新解釋底層位模式從而在類型間轉換。
-
功能:基本在原地實現任何類型的互轉(去除cv限定符除外),但不保證使用安全性。
-
分類:
- 指針和整型互轉:注意該整型需要保證size足夠容納指針數值。這同時包括:任何空指針(賦值爲nullptr的指針)和空指針常量(nullptr),可轉換成任何整型類型。
- 函數指針互轉:函數指針之間的互轉,不同類的成員函數指針的互轉。
- 其他:type類型的左值表達式可轉換成new_type &(注意無tmp變量生成,不發生拷貝);type * 可轉 const/volatile new_type *; 任何具有空指針可轉換爲其他類型指針。
-
常用例子:
// 指針到整數並轉回 int i = 7; std::uintptr_t v1 = reinterpret_cast<std::uintptr_t>(&i); int* p1 = reinterpret_cast<int*>(v1); // 到另一函數指針並轉回:已知f爲 int f() { return 0;} void(*fp1)() = reinterpret_cast<void(*)()>(f); // fp1(); 未定義行爲 int(*fp2)() = reinterpret_cast<int(*)()>(fp1); // fp2(); 安全 // int 轉 unsigned int & reinterpret_cast<unsigned int&>(i) = 42;
四種Cast的對比
通過上面的總結,我們大致可以明白四種cast接口的區別可由其名稱前綴區分。
- 僅
const_cast
可以將const、volatile類型的cv限定符去掉。 - 僅
dynamic_cast
會做運行時動態類型檢查,它主要用於多態類型的繼承層級互轉。 static_cast
使用範圍較廣,安全性較高。但當支持多態類的繼承關係轉換時,不會做運行時檢查,存在一定的安全性問題。注意用static_cast
做轉換時,生成的新對象是原對象的拷貝版本,只是執行了類型轉換。reinterpret_cast
在編譯器層面解構了原類型,以新類型的觀點去解釋原類型的各個字節,故基本可以做任何類型轉換,例如不同類型函數指針互轉,指針與整型互轉等等。與static_cast
不同但與const_cast
類似的是,這種轉換不會對原值的執行拷貝,故若能正確轉回原類型,便能恢復變成原對象。很明顯,reinterpret_cast
安全性問題較大。
參考資料
[1]. cppreference. https://en.cppreference.com/w/ 2020,2,22