現代C++語言(C++11/14/17)特性總結和使用建議(一)

 

C++語言在歷史上經過了很多次的演進。最早的時候,C++語言沒有模板、STL、異常等特性,之後加入這些特性形成大多數人所熟悉的C++98/03標準。在此之後,C++經過10多年又孕育出了擁有衆多革命性變化的C++11標準(在標準正式發佈前,被稱爲C++0x)。C++11包括了約140個新特性和約600個缺陷的修正。由於其變化實在太大,被很多人稱爲“現代C++語言”(Modern C++ Language),以和之前的經典C++語言作區分。在C++11後,標準委員會又發佈了C++14和C++17標準,其內容大多是對C++11特性的一些修改和補充。

業界關於C++11/14/17新特性的書籍已有不少,網上的介紹文章更多。但是這些書籍和文章都只是對其中的一部分特性作有挑選的介紹。對於現代C++這種內容龐雜的語言改進來說,也不可能通過某一本書進行完全覆蓋。開發人員在實際編程中碰到特定特性的問題,還是需要靠網絡搜索來學習和解決。

本文試圖從我司實際角度來闡述C++新特性所帶來的影響,主要目標針對我司的存量產品嵌入式開發領域,挑選一些開發人員應當主動了解並在項目中多加使用的特性進行說明,並且儘可能的給出其他外部資源的鏈接,達到“授之以漁”的目的。

另一方面,現代C++語言的新特性雖然廣受讚譽,但如果使用不當,也會掉入新的陷阱,產生令人頭疼的問題,而這些陷阱卻很少有文章提起。本文試圖從個人經驗的角度,多描述一下實際使用建議和注意事項。

C++11/14/17新特性概述及編譯器支持

首先,哪些編譯器支持哪些新特性?這個問題可不好回答。我們沒有辦法籠統地說“xxx版本的編譯器支持C++11”或“xxx版本的編譯器支持到C++14”。因爲,每個編譯器版本都是選擇對部分特性的支持,甚至對單個特性,也可能只是支持其一部分場景。比如,常用的gcc 4.9版本,不光支持大部分的C++11特性,也支持一部分C++14特性,甚至還提前支持了個別C++17和C++20特性。因此要深入瞭解某特性支持狀況,需要查詢相關資料。

這裏給出一個非常全面的主流編譯器支持C++特性現狀列表,地址如下:
https://en.cppreference.com/w/cpp/compiler_support

這個頁面有很多好處:每個特性都給出了標準建議文檔的鏈接,有些還包括在線的特性說明頁面鏈接,可以方便的跳轉到相應內容來進一步的瞭解語言特性。建議大家收藏這個頁面供日後查詢。

下文在介紹各個特性時,努力把更常用的特性放在前面。但是,由於各個項目的業務背景差別,在應用語言特性的需求上肯定大不一樣,因此大家可以挑選自己感興趣的內容重點閱讀。

本文分開介紹C++核心語言特性和標準庫特性,但標準庫(STL)的很多改進同樣是非常重要和常用的,不應當被忽視。

C++11新特性(語言核心)

auto關鍵字

在C++11之前,auto關鍵字用來指定存儲期。在新標準中,它的功能變爲類型推斷。auto現在成了一個類型的佔位符,通知編譯器去根據初始化代碼推斷所聲明變量的真實類型。各種作用域內聲明變量都可以用到它。例如,名字空間中,程序塊中,或是for循環的初始化語句中。

 

1.png

使用auto通常意味着更短的代碼(除非你所用類型是int,它會比auto少一個字母)。試想一下當你遍歷STL容器時需要聲明的那些迭代器(iterator)。現在不需要去聲明那些typedef就可以得到簡潔的代碼了。

 

2.png

需要注意的是,C++11中的auto不能用來聲明函數的返回值。但如果函數有一個尾隨的返回類型時,auto是可以出現在函數聲明中返回值位置。這種情況下,auto並不是告訴編譯器去推斷返回類型,而是指引編譯器去函數的末端尋找返回值類型。在下面這個例子中,函數的返回值類型就是operator+操作符作用在T1、T2類型變量上的返回值類型。(C++14改進了這些限制)

 

3.png

auto特性可以在很大程度上簡化代碼,節約編程人員的體力,而且不容易出現錯誤,因此應當大力提倡使用。

需要注意的有兩點:第一是如果想要聲明一個引用類型,必須用auto&,同理,對於指針、const、volatile等修飾符也可以用在auto上進一步限定類型。第二是C++11不允許直接用auto聲明函數返回值,但C++14做了改進,可閱讀相關資料瞭解。

相關鏈接:https://zh.cppreference.com/w/cpp/language/auto

nullptr關鍵字和std::nullptr_t類型

以前都是用0來表示空指針的,但由於0可以被隱式類型轉換爲整型,這就會存在一些問題。關鍵字nullptr是std::nullptr_t類型的值,用來指代空指針。nullptr和任何指針類型以及類成員指針類型的空值之間可以發生隱式類型轉換,同樣也可以隱式轉換爲bool型(取值爲false)。但是不存在到整型的隱式類型轉換。

 

4.png

nullptr可以有效地減少二義性,防止錯誤,應當大力鼓勵使用。需要注意的是,由於以前的NULL仍然是有效的,而且有些歷史代碼中存在把NULL當作整型數字0的不良習慣,應當要求所有新代碼都只用nullptr,徹底廢除NULL,不要兩者混用。

相關鏈接:https://zh.cppreference.com/w/cpp/language/nullptr

Range-based for loops(基於範圍的for循環)

爲了在遍歷容器時支持“foreach”用法,C++11擴展了for語句的語法。用這個新的寫法,可以遍歷C類型的數組、初始化列表以及任何重載了非成員的begin()和end()函數的類型。

如果你只是想對集合或數組的每個元素做一些操作,而不關心下標、迭代器位置或者元素個數,那麼這種foreach的for循環將會非常有用。

 

5.png

基於範圍的for循環已經在其他語言中被廣爲接受,相比於原來手工遞增/遞減循環變量的做法,可以減少越界、溢出、意外修改循環變量等很多問題,應該大力推廣替代老式的for循環。需要注意的是,和以前一樣,在循環內部嚴禁修改正在被遍歷的容器大小(比如在循環內刪除元素)。另外,對於自定義的容器類,都應該顯式定義begin和end函數讓其可以被用於新式的for循環。

由於新式的for循環內部也是會給聲明的變量賦值的,因此某些性能方面的考慮仍然值得注意。比如,如果不希望拷貝容器內部的元素,那麼在聲明工作變量時應當用auto&而不是auto。

相關鏈接:https://zh.cppreference.com/w/cpp/language/range-for

constexpr(編譯期常量類型)

C++11中對編譯時期常量的回答是constexptr,及常量表達式(const expression),例如:

即在函數表達式前面加上constexpr關鍵字即可。有了常量表達式這樣的聲明,編譯器可以在編譯時期對GetConst()表達式進行值計算(evaluation),從而將其視爲一個編譯時期的常量。在C++11中,常量表達式實際上可以左右的實體不限於函數,還可以作用於數據聲明,以及類的構造函數等。

 

9.png

通常我們可以在函數返回類型前加入關鍵字constexpr來使其成爲常量表達式函數。不過並非所有的函數都有資格成爲常量表達式函數。事實上,常量表達式函數的要求非常嚴格,總結來說,大概有以下幾點:
函數體只有單一的return返回語句。
函數必須有返回值(不能是void函數)。
在使用前必須已有定義。
return返回語句表達式中不能使用非常量表達式的函數、全局數據,且必須是一個常量表達式。(C++14放寬了這些要求)

以前的const修飾符並不能保證產生編譯期常量,因此某些用法,比如用const常量聲明數組長度不能保證可行。相比之下,constexpr類型具有更強的編譯期檢查,可以在編譯階段就發現常量能否在編譯階段確定。應當要求所有新代碼中的編譯期常量都聲明爲constexpr。

相關鏈接:https://zh.cppreference.com/w/cpp/language/constexpr

模板別名

在C++中,使用typedef爲類型定義別名,比如:

 

6.png

在C++11中,定義別名已經不再是typedef的專屬能力,使用using同樣也可以定義類型的別名,而且從語言能力上看,using絲毫不比typedef遜色。

 

7.png

在上面的例子中,使用了C++11標準庫的is_same模板類來幫助我們判斷2個類型是否一致。在使用模板編程的時候,using的語法甚至比typedef更加靈活,比如:

8.png

 

在這裏,我們“模板式”的使用了using關鍵字,將std::map<T, char*>定義爲一個MapString類型,之後我們還可以使用類型參數對MapString進行類型的實例化,而使用typedef將無法達到這樣的效果。

從功能和可讀性上看,using模板別名都比原來的typedef要更好,因此新寫的代碼都應使用using來取代typedef定義。

相關鏈接:https://zh.cppreference.com/w/cpp/language/type_alias

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章