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

二進制數字和數字分隔符

除了原有的十進制、十六進制和比較不常用的八進制表示方法之外,C++程序員現在還可以使用二進制表示常量了。二進制常量以前綴0b(或0B)開頭,二進制數字緊隨其後。

在英美兩國,在寫數字時,我們習慣於使用逗號作爲數字的分隔符,如:$1,000,000。這些數字分隔符純爲方便讀者,它提供的語法線索使我們的大腦在處理長串的數字時更加容易。

基於完全相同的原因,C++標準委員會爲C++語言增加了數字分隔符。數字分隔符不會影響數字的值,它們的存在僅僅是爲了通過分組使數字的讀寫更容易。

使用哪個字符來表示數字分隔符呢?在C++中,幾乎每個標點字符都已經有特定的用途了,因此並沒有明顯的選擇。最終的結果是使用單引號字符,這使得百萬美元在C++中寫作1’000’000.00。記住,分隔符不會對常量的值有任何影響,因此,1’0’00’0’00.00也是表示百萬。

下面是一個結合了這兩種新特性的例子:

1.png

可以看到,當二進制數字不長的時候,使用二進制字面量和分隔符可以有效地改善代碼可讀性,值得推廣。但如果是很長的二進制數字,有可能用16進制會顯得更簡短一些。

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

C++14新特性(標準庫)

std::make_unique

C++11標準中有std::make_shared可以讓智能指針shared_ptr的初始化和對象構造在一條語句中完成,現在std::make_unique也被加入了標準,讓unique_ptr也可以用同樣的方式初始化。

2.png

相比原來的構造方式,std::make_unique顯然要更加安全,應當要求新代碼使用這種新的初始化方式。

相關鏈接:https://zh.cppreference.com/w/cpp/memory/unique_ptr/make_unique

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

Structured Binding(結構化綁定)

C++11提供的std::tie雖然好用,但是隻能用於已經被聲明的變量。有時爲了解析一個函數返回的元組,不得不先聲明很多個臨時變量。結構化綁定允許用auto []自動聲明很多個對應類型的變量,大大簡化代碼:

3.png

結構化綁定可以被用於std::pair、std::tuple、初始化列表(std::initializer_list)、數組、只包含簡單成員的結構體、以及所有定義了get<N>的自定義類型。靈活使用結構化綁定,可以讓C++代碼看起來像動態類型語言(如Python)。

4.png

結構化綁定讓代碼更簡潔,值得推廣。

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

Init Statement for if/switch(if/switch語句中的初始化語句)

我們知道C++的for語句頭部是可以聲明變量的,該變量作用域只限定於for循環體內。現在if/switch語句也支持同樣的用法了,這可以有效地避免臨時變量之間重名,對於判斷條件包含函數返回值時尤其有用。

5.png

當和結構化綁定結合使用時,這個特性能發揮更大的威力:

6.png

這個特性減少了代碼中的命名個數,增加了可讀性,值得推廣。

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

Inline Variables(內聯變量)

C++11的constexpr允許類的靜態成員常量直接在類定義內部初始化,但是對於靜態成員變量就不行了。內聯變量特性允許非常量也在頭文件中初始化,編譯器會保證各個翻譯單元看到的都是同一個變量實體。

7.png

需要用到這個特性的場景不多,很難說把靜態成員初始化寫在頭文件中是不是一個值得推薦的做法。一旦遇到頭文件不對齊,可能會造成一些相當麻煩的錯誤。但對於純頭文件的工具庫來說,它還是很有用的。

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

Nested namespace definition(嵌入名字空間定義)

原來對於多層嵌套的名字空間定義必須寫多層大括號,有了這個特性,現在可以這樣寫了:

8.png

這是一個影響不大的特性,有了它,在少數場景下可以簡化一些代碼。

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

constexpr if-statements(靜態條件語句)

這個特性可以讓編譯器在編譯期就決定哪個分支被運行,去掉運行時的判斷開銷。比如下面這個get_value函數模板,可以在編譯期確定對於指針返回其指向的值,對於非指針直接返回其本身。

9.png

以前沒有constexpr if時,用戶需要使用函數重載和模板技巧來達成同樣的效果,可能會藉助C++11中的std::enable_if。現在使用constexpr if,可以讓代碼看起來更直觀、容易理解。下面這段代碼,用一個模板函數完成了以前需要寫多個模板特例化才能完成的成員選擇功能:

10.png

不過,constexpr if也帶來了一個具有爭議的問題:它是否增加代碼的圈複雜度?以前在編譯期選擇分支通常用#ifdef預處理指令,它是不會被算作圈複雜度的。但是constexpr if就難說了。從可讀性上講,constexpr if確實和普通條件分支一樣需要閱讀者理解其判斷過程,只不過沒有運行時的開銷。因此,對於是否應該全面推廣使用constexpr if,很難給出統一的建議,只能留待開發人員根據具體場景來選擇。

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

Template Argument Deduction for Class Templates(類模板的模板參數推導)

在以前,函數模板可以根據傳入參數的類型自動實例化,但類模板在使用時必須要顯式指定模板參數類型。C++17修改了模板推導規則,允許類模板也根據構造函數的參數類型自動實例化。

11.png

有了這個特性,很多的輔助函數模板可以省掉了,比如std::make_pair、std::make_tuple(當然,對於標準庫中已有的輔助函數,繼續用它們也沒壞處)。以後使用類模板只需直接調用構造函數就可以。

但是需要注意的是,對於智能指針,輔助函數(std::make_unique、std::make_shared)可不能省,它們的作用並不只是用來推導類型,還能保證對象構造和指針初始化同時進行。

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

Non-type Template Parameters with auto(auto關鍵字用於非類型模板參數)

以前模板的非類型參數需要顯式的寫出類型(一般是int或者bool),現在允許讓編譯器來自動推導了。

12.png

非類型參數的類型變化的場景並不多,所以這個特性能派上用場的地方也有限。一般來說還是推薦寫出確定的類型。

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

C++17新特性(標準庫)

std::string_view

在很多編程語言中,字符串都是不可修改(immutable)的,這樣就可以放心的讓同一個字符串內容被多處引用共享。但是C++的std::string卻是自帶獨立內存空間的可修改容器,這使得在某些場景下性能會劣化。比如,想要函數返回一個字符串,很難避免return時它被複制一次。當然,C++11以後我們可以用右值引用等方法來做一定程度的優化,但還是很難解決多處共享同一個字符串等場景的問題。

std::string_view爲此而誕生,它不會對字符串內容進行任何修改,且沒有內存管理的功能,僅僅只是將C的原生字符串做了一下包裝。並且帶有查找、越界檢查、去除前綴/後綴、運算符重載等方便的功能。

13.png

由於實際代碼中很多字符串是並不打算被修改的,std::string_view是一個非常值得推廣的價值特性。相比於C原生字符串那些難以受控的指針操作,使用std::string_view能使代碼更簡潔、更安全、更易理解,同時也沒有性能的下降。但要注意的是,由於std::string_view不負責做任何內存管理,而且允許很多個std::string_view共享同一片內存,程序員必須注意內部字符串空間的釋放,以防止內存泄露和訪問野指針。

相關鏈接:https://zh.cppreference.com/w/cpp/string/basic_string_view

std::optional

程序員經常碰到的一個問題是:一個函數有可能返回一個對象,也有可能返回失敗,這種函數如何聲明?以前通常有兩種辦法:1、函數返回錯誤碼,同時用一個引用參數來帶出對象。這種方式給調用者帶來的麻煩是,必須要多寫一行代碼聲明一個對象用於接收返回值。2、函數返回一個對象的指針,如果爲空指針則表示失敗。這種方法的一大隱患是對象的內存由誰來釋放?稍有不慎就可能出現內存泄露或者重複釋放。

C++17的標準庫給出了一種更好的解決方案:std::optional模板。函數返回std::optional時,表示既有可能含值也有可能不含值。調用者可以用bool表達式來判斷其是否含值,也可以用value_or函數來提供一個當不含值時的默認值。內部對象的生命週期由std::optional來管理,當std::optional對象被釋放時其內部管理的對象也自動被釋放。

14.png

std::optional可以很有效的簡化代碼,並防止安全問題,在寫查找功能的函數時尤其有用。但是使用std::optional時也要注意在構造時如何減少拷貝開銷,一般可以使用std::make_optional或者常量表達式來完成構造。另一方面,由於std::optional析構時會自動把內部對象也給析構掉,使用者應注意不要訪問野指針。

相關鏈接:https://zh.cppreference.com/w/cpp/utility/optional

std::variant

C++中的標準庫容器都要求所有內部元素類型是一致的,但是實際開發中經常碰到需要把多個不同類型對象放到同一個容器中管理的需求,比如一個JSON對象中的value元素,既有可能是整型也有可能是浮點型。之前對這種場景有兩種解決方案:1、對所有元素類型定義一個共同基類,容器中存放基類的指針。這是經典的面向對象多態設計方法,但在C++語言中的問題是這樣做以後對象的內存就只能手工管理,得手寫代碼在合適的時期new和delete。2、容器中只存放無類型的內存(char*或者void*),由使用者根據上下文信息來將其轉換爲合適的對象類型。這種方法是不推薦的,一旦類型轉換錯誤,會出現難以定位的運行時錯誤。

C++17提供了一種新的工具:std::variant。它允許多種不同的可能類型存放於同一個std::variant對象中,但同一時間內std::variant只會有某一種類型的對象。使用者可以通過index函數來檢查當前類型,也可以用std::get指明類型序號或者類型名來獲取其中的對象。std::variant對類型有強校驗,當類型不符合時會觸發編譯錯誤或者拋出異常。

15.png

std::variant對於不希望手工管理生命週期的多類型小對象來說是個很好的選擇。使用std::variant並不會妨礙多態的優點,程序員仍然可以通過基類對象引用來獲取std::variant的內部對象(自動upcast)並對其執行虛函數。

相關鏈接:https://zh.cppreference.com/w/cpp/utility/variant

 

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