C 语言中std::array的神奇用法总结
std::array是在C 11標準中增加的STL容器,它的設計目的是提供與原生數組類似的功能與性能。也正因此,使得std::array有很多與其他容器不同的特殊之處,比如:std::array的元素是直接存放在實例內部,而不是在堆上分配空間;std::array的大小必須在編譯期確定;std::array的構造函數、析構函數和賦值操作符都是編譯器隱式聲明的……這讓很s多用慣了std::vector這類容器的程序員不習慣,覺得std::array不好用。
但實際上,std::array的威力很可能被低估了。在這篇文章里,我會從各個角度介紹下std::array的用法,希望能帶來一些啟發。
本文的代碼都在C 17環境下編譯運行。當前主流的g 版本已經能支持C 17標準,但是很多版本(如gcc 7.3)的C 17特性不是默認打開的,需要手工添加編譯選項-std=c 17。
自動推導數組大小
很多項目中都會有類似這樣的全局數組作為配置參數:
uint32_t?g_cfgPara[]?=?{1,?2,?5,?6,?7,?9,?3,?4};當程序員想要使用std::array替換原生數組時,麻煩來了:
array?g_cfgPara?=?{1,?2,?5,?6,?7,?9,?3,?4};??//?注意模板參數“8”程序員不得不手工寫出數組的大小,因為它是std::array的模板參數之一。如果這個數組很長,或者經常增刪成員,對數組大小的維護工作恐怕不是那么愉快的。有人要抱怨了:std::array的聲明用起來還沒有原生數組方便,選它干啥?
但是,這個抱怨只該限于C 17之前,?C 17帶來了類模板參數推導特性,?你不再需要手工指定類模板的參數:
看起來很美好,但很快就會有人發現不對頭:數組元素的類型是什么?還是std::uint32_t嗎?
有人開始嘗試只提供元素類型參數,讓編譯器自動推導長度,遺憾的是,它不會奏效。
好吧,暫時看起來std::array是不能像原生數組那樣聲明。下面我們來解決這個問題。
用函數返回std::array
問題的解決思路是用函數模板來替代類模板——因為C 允許函數模板的部分參數自動推導——我們可以聯想到std::make_pair、std::make_tuple這類輔助函數。巧的是,?C 標準真的在TS v2試驗版本中推出過std::make_array,?然而因為類模板參數推導的問世,這個工具函數后來被刪掉了。
但顯然,用戶的需求還是存在的。于是在C 20中,?又新增了一個輔助函數std::to_array。
別被C 20給嚇到了,這個函數的代碼其實很簡單,我們可以把它拿過來定義在自己的C 17代碼中[1]。
細心的朋友會注意到,上面這個定義與C 20的推薦實現有所差異,這是有目的的。稍后我會解釋這么干的原因。
現在讓我們嘗試下用新方法解決老問題:
auto?g_cfgPara?=?to_array({1,?2,?5,?6,?7,?9,?3,?4});??//?類型不是uint32_t?不對啊,為什么元素類型不是原來的std::uint32_t?
這是因為模板參數推導對std::initializer_list的元素拒絕隱式轉換,如果你把to_array的模板參數從int改為uint32_t,會得到如下編譯錯誤:
Hoho,有點慘是不,繞了一圈回到原點,還是不能強制指定類型。
這個時候,之前針對std::array做的修改派上用場了:我給to_array_impl增加了一個模板參數,讓輸入數組的元素和返回std::array的元素用不同的類型參數表示,這樣就給類型轉換帶來了可能。為了實現轉換到指定的類型,我們還需要添加兩個工具函數:
這兩個函數和to_array的區別是:它帶有3個模板參數:第一個是要返回的std::array的元素類型,后兩個和to_array一樣。這樣我們就可以通過指定第一個參數來實現定制std::array元素類型了。
auto?g_cfgPara?=?to_typed_array({1,?2,?5,?6,?7,?9,?3,?4});??//?自動把元素轉換成uint32_t這段代碼可以編譯通過和運行,但是卻有類型轉換的編譯告警。當然,如果你膽子夠大,可以在to_array_impl函數中放一個static_cast來消除告警。但是編譯告警提示了我們一個不能忽視的問題:如果萬一輸入的數值溢出了怎么辦?
auto?g_a?=?to_typed_array({256,?-1});??//?數字超出uint8_t范圍編譯器還是一樣的會讓你編譯通過和運行,g_a中的兩個元素的值將分別為0和255。如果你不明白為什么這兩個值和入參不一樣,你該復習下整型溢出與回繞的知識了。
顯然,這個方案還不完美。但我們可以繼續改進。
編譯期字面量數值合法性校驗
首先能想到的做法是在to_array_impl函數中放入一個if判斷之類的語句,對于超出目標數值范圍的輸入拋出異常或者做其他處理。這當然可行,但要注意的是這些工具函數是可以在運行期調用的,對于這種常用的基礎函數來說,性能至關重要。一旦在里面加入了錯誤判斷,意味著運行時的每一次調用性能都會下降。
理想的設計是:只有在編譯期生成的數組才進行校驗,并且報編譯錯誤。但運行時調用函數時不要加入任何校驗。
可惜的是,至少在C 20之前,沒有辦法指定函數只允許在編譯期執行[2]。那有沒有其他手段呢?
熟悉C 的人知道:C 的編譯期處理大多可以用模板的trick來完成——因為模板參數一定是編譯期常量。因此我們可以用模板參數來完成編譯期處理——只要把數組元素全部作為模板的非類型參數就可以了。當然,這里有個問題:模板的非類型參數的類型怎么確定?正好C 17提供了auto模板參數的功能,可以派上用場:
總結
以上是生活随笔為你收集整理的C 语言中std::array的神奇用法总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电脑接口可以加外置显卡吗(主机可以接外置
- 下一篇: 被ddos攻击(ddos挟持)