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