一文講清C/C++ Const/Const_Cast/Constexpr
本文轉載自微信公眾號「碼磚雜役」,作者我不想種地 。轉載本文請聯系碼磚雜役公眾號。
很多人搞不清const、const_cast、constexpr的用法,稀里糊涂地用。一般而言,即使亂用,問題也不大,因為錯大發了會崩,崩了自然會被修正,不崩自然也就沒事。但作為一個有追求的專業程序員,自當聞過則喜,搞清楚弄明白。
一、const
C語言的const用法
先講const,這玩意兒怎么翻譯我也拿不準,C語言中該關鍵字的用法比較簡單,大概有如下幾種用法:
[1] 修飾普通變量:變量只讀,在程序運行過程中不可修改。
- const int i = 100; //i is read only
- i = 200; //compile error, variable i can not assignable
[2] 修飾指針 const T* p:表示不能通過p去修改p指向對象的內容,另一方面只能通過p調用T類的const成員函數
- const struct Foo *f = new Foo;
- f->dataX = 100; //compile error
- const char* p = "abc";
- p[1] = 'x'; //compile error
- f->nonconst_member_function(); ///compile error (后面再講)
[3] 修飾指針 T* const p:表示指針只能在初始化時設置指向,之后便不能修改指向。
- char s1[] = "abc";
- char s2[] = "xyz";
- char* const p = s1;
- p = s2; //compile error
[4] 修飾指針 const T* const p:表示既不能通過p修改它指向的對象,又不能更改p的指向。
- const char* const p = "abc";
- p[1] = 'B'; //compile error
- p = "xyz"; //compile error
[5] 修飾函數參數:c語言中const修飾參數反映的含義同上所述
小結:C語言中,const的用法差不多就這些,比較簡單。
C++擴充了const的用法
[1] 修飾成員變量:const成員變量只能在初始化列表里做初始化,程序運行中不可修改;如果是const整型,則可以C++11標準之后直接初始化。
- struct Foo
- {
- Foo() : PI(3.15) {} // PI is initialized by initializer list
- const int c = 100; //C++11 support
- const float PI;
- };
[2] 修飾成員函數:表示該成員函數是只讀函數,不會修改默認參數this的成員變量,如果修改會編譯報錯。
- class Foo
- {
- int m_money;
- public:
- int get_money() const //✅
- {
- return m_money;
- }
- int set_money(int money) const //❌
- {
- m_money = money; //修改了this->m_money;需去掉函數const修飾
- }
- };
[3] 修飾引用:引用是C++才有的語法特征,引用是別名,本質上跟指針差不多,所以const修飾引用跟修飾指針的語義和約束差不多。
- Foo f;
- const Foo& r = f;
- r.m_data = 1; //compile error
[4] C++中對const修飾指針的補充
- struct Foo
- {
- int const_member_function() const { return m_data; }
- int non_const_member_function(int data) { m_data = data; }
- int m_data;
- };
- int main()
- {
- const Foo* f = new Foo;
- f->const_member_function(); //OK
- f->non_const_member_function(); //compile ERROR
- return 0;
- }
為什么呢?因為const成員函數相當于承諾不會修改this的成員變量,而該承諾會被編譯器檢查,如果沒有履行承諾,則編譯器會報錯。而const Foo* f意味著不能通過f去修改f指針指向變量的內部值。
通過f->data = 1的方式肯定是不行。
另一方面,你只能通過f去調用它的const成員函數,因為const成員函數的語義就是不會修改this的值,編譯器很容易執行這個校驗。
const修飾參數
const可以修飾普通參數,也可以修飾指針/引用參數,因為形參是實參的副本,所以const修飾普通參數其實沒什么意義,我們著重講講const修飾指針/引用參數。
比如標準C庫函數strcpy的簽名:char *strcpy(char * dst, const char * src);
dst表示目標地址,src表示源串,const修飾了源串,這是因為從源串拷貝到目標串,不需要修改源串內容,這相當于向strcpy調用者承諾:
放心大膽的調用吧,strcpy函數實現保證不會修改src的內容,編譯器會執行這種檢查。
這樣,在review代碼的時候,如果想追蹤src在哪里被修改了,當看到strcpy的簽名,就不用打開函數去看實現,只要不違背承諾,肯定不是這個函數內改動了src。
const char *src是一種承諾,也是一種約束。調用的地方,const char*形式的形參,既傳const char*實參,也可以傳char*實參,因為參數const char*是更強的承諾。
但反之不成立。比如第一個參數dst是不帶const的,那么如果有一個變量類型為const char* p,那不能把p作為第一個參數傳遞進strcpy,編譯不過。
因為strcpy不承諾不修改dst,是一個更弱的承諾,只有聲明為const指針的參數,才能傳遞const指針實參。
const其他
const還可以修飾返回值,還可以跟extern結合,但這些都是一些小語法技巧,一般開發用不太到,真碰到再查不遲。
二、const_cast
const_cast有什么用?
const是C++的一個強制轉換,它用來去掉const屬性,比如:
- Foo foo;
- const Foo *f1 = &foo;
- Foo* f2 = const_cast<Foo*>(f);
- Foo* f3 = (Foo*)f;
const_cast的作用跟強轉差不多,C++加const_cast主要是為了功能完整性,const_cast作用于引用跟作用于指針差不多。
為什么說const_cast幾乎都反應接口設計有問題
程序設計要言行一致,遵守承諾,這意味著:不應該把參數聲明為const指針,而函數實現里借助強制去掉const屬性。
首先,這樣做是危險的,比如const char* p = "abc"; p指向常量字符串被作為參數傳遞,被強轉+修改,則會導致程序crash。
其次,這樣做是分裂的,因為你加const修飾相當于讓編譯器幫你執行檢查,以便在你違背承諾的時候通過編譯期檢查報錯提醒你,但在它真正向你報錯的時候,你又說別管啦,老子就是要蠻干。
const_cast或者通過c風格強轉,基本上都暴露出設計上的問題。
設計良好的程序基本上不需要const強轉。因為const約束在調用鏈會傳播,所以,你需要一以貫之的遵守約定,找到導致需要const強轉的錯誤源頭,這可能會多費一點時間,但它是值得的。
三、constexpr
const沒有區分編譯期常量和運行期常量,constexpr是C++11開始提出的關鍵字,被限定為編譯器常量,其意義與14版本有一些區別。
C++11中的constexpr指定的函數返回值和參數必須要保證是字面值,而且必須有且只有一行return代碼,這給函數的設計者帶來了更多的限制,比如通常只能通過return 三目運算符+遞歸來計算返回的字面值。
而C++14中只要保證返回值和參數是字面值就行了,函數體中可以加入更多的語句,方便了更靈活的計算。
這里我們主要講constexpr和const的區別。
constexpr可以用來修飾變量、函數、構造函數。一旦以上任何元素被constexpr修飾,那么等于說是告訴編譯器 “請大膽地將我看成編譯時就能得出常量值的表達式去優化我”。
- constexpr func()
- {
- return 10;
- }
- int main()
- {
- int arr[func()];
- }
編譯期大膽地將func()做了優化,在編譯期就確定了func計算出的值10而無需等到運行時再去計算。
這就是constexpr的第一個作用:給編譯器足夠的信心在編譯期去做被constexpr修飾的表達式的優化。
constexpr還有另外一個特性,雖然它本身的作用之一就是希望程序員能給編譯器做優化的信心,但它卻猜到了自己可能會被程序員欺騙,而編譯器并不會對此“惱羞成怒”中止編譯。
四、結論
C/C++程序應該積極的使用const/constexpr,什么叫積極使用?只要有可能,那么我們就應該用const/constexpr。
只要可能就應該用xx,這種話一般而言都是錯的,但用在const/constexpr卻很正確,因為使用const/constexpr基本上都會讓你的程序更健壯、更快,const修飾的整型變量,在gcc開優化選項的時候,有可能被直接編譯到匯編代碼指令,而非生成一個變量,而constexpr的優化作用在前面一節已經闡述。
與之對應的是:只要有可能,就不要使用const_cast,它基本上都反映了接口設計上的問題。
就醬,信不信隨你!