const_cast是一個運算符,和dynamic_cast、static_cast、reinterpret_cast一樣都是用於類型的轉換的,本來想將這四個一起寫的,但是查了const_cast相關的資料,發現const_cast可以牽扯到的也不少,所以單獨用一篇博客說明了。
1. 用法
const_cast的作用很簡單,就是去除或增加類型中的const屬性或volatile屬性,注意這裏也可以用於增加這兩種屬性。其原型如下:
const_cast<type_id> (expression)
雖然這裏用了“去除”,但實際上並沒有改變原類型的const屬性或volatile屬性,而是提供了一個接口(指針或引用),使得我們可以通過這個新的接口(變量)來改變原類型的值。在const_cast中,type_id也只能是指針或者引用,因爲只有通過指針或引用才能得到原類型的地址,從而對其內存內容進行修改。
個人覺得const_cast就有點類似於顯式類型轉換,將原來變量起了新的別名或者將其地址賦給新另一個指針,只不過它是限定源類型和目的類型是要一樣的。更詳細的介紹可以看這裏:const_cast 轉換。
下面是使用的例子:
#include <iostream>
using namespace std;
int main()
{
const int a = 10;
// 強制轉換
int *p = (int *)(&a);
*p = 20;
cout << "a = " << a << ", *p = " << *p << endl;
// const_cast到指針
int *p2 = const_cast<int *>(&a);
*p2 = 30;
cout << "a = " << a << ", *p = " << *p << endl;
// const_cast到引用
int &p3 = const_cast<int &>(a);
p3 = 40;
cout << "a = " << a << ", *p = " << *p << endl;
return 0;
}
上面舉出了三種轉換的方法,第一種是將取地址後保存,後面兩種則分別用了const_cast的指針和引用方式,這三種方式都可以修改const變量a對應內存的內容。但是上面程序的輸出卻可能與我們想象中的不一樣:
a = 10, *p = 20
a = 10, *p = 30
a = 10, *p = 40
可以看到,即使設置“*p = 20”、“*p2 = 30”、“p3 = 40”,在三次輸出中,a的值都是最初定義的值10,並沒有改變,這是爲什麼呢?原因就在於接下來要介紹的概念:常量摺疊。
2. 常量摺疊(const folding)
常量摺疊與編譯器的工作原理有關,是編譯器的一種編譯優化。在編譯器進行語法分析的時候,將常量表達式計算求職,並用求得的值來替換表達式,放入常量表。所以在上面的例子中,編譯器在優化的過程中,會把碰到的a(爲const常量)全部以內容10替換掉,跟宏的替換有點類似。
與常量摺疊緊關的概念是複寫傳播(copy propagation),這裏就不講了。
雖然在運行階段a的內容確實是被改變了,但它在打印的部分已被替換爲10了,導致打印出來的值與我們期望的就不一樣了。這讓我想到在收集資料時看到的這句話:
const是一個編譯時約定,而不是運行時約定。
另外,常量摺疊只對原生類型其作用,對我們自定義的類型,是不會起作用的。下面是我測試的例子:
#include <iostream>
#include <stdio.h>
using namespace std;
typedef struct _Test {
int a;
_Test() {
a = 10;
};
} Test;
int main()
{
const Test b;
Test *b1 = const_cast<Test *>(&b);
cout << "b = " << b.a << endl;
b1->a = 100;
cout << "b = " << b.a << ", *b1 = " << b1->a << endl;
return 0;
}
輸出內容如下:
b = 10
b = 100, *b1 = 100
可以看到對於自定義的類型,是不存在常量摺疊的想象的,估計是對於自定義類型並不會進行相應的優化操作吧。
3. const_cast的使用場景
正常情況下,當我們在創建變量時加上const關鍵字則說明我們是不希望他被改變的,不加上const則表明我們存在改變它的可能性。那const_cast究竟在什麼情況下會用到呢?因爲正常情況下我們寫代碼函數什麼的肯定是對變量的修改情況有了解的。
經過搜索,發現const_cast似乎是應該用在這種情況的:假如函數A中調用B函數,B中定義參數是const的而A傳入的變量是非const,這個時候我們就可以用const_cast給傳入的變量加上const屬性了。反過來B中定義參數是非const而A傳入的變量是const的,則我們可以用const_cast將傳入變量的const屬性去掉了(當然這種情況下就算不加const屬性也是可以的)。
#include <iostream>
using namespace std;
void not_const_char(char *str)
{
cout << str + 5 << endl;
}
void const_char(const char *str)
{
cout << str + 5 << endl;
}
int main()
{
const char str[] = "abcdefgggg";
char str1[] = "efggggeeee";
not_const_char(str1); // ok
// not_const_char(str); // compile fail
not_const_char(const_cast<char *>(str)); // ok
const_char(str1); // ok
const_char(str); // ok
return 0;
}
可以看到註釋掉的調用“not_const_char(str)”是編譯失敗的,也就是說如果將const屬性的變量傳入到非const的函數中,編譯不會通過(const是編譯時約定,它預期在傳入的函數中會對其內容修改),但用const_cast對傳入的變量去掉const屬性後再傳入,就能正常編譯通過了。
搜索了下公司的項目代碼,簡略看到有兩種情況用到了const_cast。
第一種是在單例模式中的函數修改類中的靜態成員對象時:
const_cast<SomethingStatistic&>(statistic_).inc_recv_num();
第二種如下:
void App::Fish(uint64_t id, const char* data, uint32_t len) {
...
reinterpret_cast<CHeader*>(const_cast<char*>(data) +
sizeof(FHead) - sizeof(CHeader));
...
}
第一種我也不太理解,但看到在protobuf的源碼中也有類似的用法,希望有懂的人可以說明下。第二種就有點類似於我上面所舉的例子,只是這裏需要對data數據轉換成需要的類型:CHeader是非const,而傳入的data是const的,所以在需要去掉data的const屬性吧。
以上是全部內容,學無止境,說得不對的請指正,有什麼更深的瞭解也希望大家能在評論裏說出來。