C++如何優(yōu)雅的進(jìn)行錯(cuò)誤處理?
使用expected進(jìn)行錯(cuò)誤處理
通過 try catch 以拋出異常的方式進(jìn)行處理,這種方式很多人會(huì)覺得看起來可讀性很差(包括我),并且由于缺少異常規(guī)約(某些異常必須捕獲),容易出現(xiàn) bug,而且異常的傳遞很多時(shí)候可能伴隨動(dòng)態(tài)分配內(nèi)存,這是一筆不小的開銷。
通過 error_code 作為返回值判斷,這種方式雖然看似簡單易用,但是由于 C++ 中并未對(duì) error_code 作過多的規(guī)范,使用起來并不方便,很多時(shí)候還是傾向于自定義一些枚舉作為自己的 error_code,但是由于缺少多返回值的語法(當(dāng)然如果C++版本比較高可用使用tuple以及解構(gòu)的語法實(shí)現(xiàn)),如果把錯(cuò)誤碼作為返回值,那么原本的數(shù)據(jù)返回就只能函數(shù)參數(shù)傳遞引用的形式返回了。當(dāng)然,如果不考慮函數(shù)返回值的具體錯(cuò)誤信息,可以使用 C++17 的 optional 。
由于 optional 無法包含具體的錯(cuò)誤信息,expected 橫空出世,在 C++23 開始納入標(biāo)準(zhǔn)。如果你的C++版本較低,可以使用第三方開源的 expected 庫:
關(guān)于 C++23 中的新特性,包括 expected 庫的使用,大家可以觀看 CppCon:How C++23 Changes the Way We Write Code
下面我會(huì)以一個(gè)例子把第三方庫中的 expected 庫的使用方式介紹給大家。
expected 使用實(shí)例
由于該第三方庫是 head-only 的,所以你只需要進(jìn)到GitHub倉庫把對(duì)應(yīng)的頭文件復(fù)制過來,便可引入使用。
下面是示例代碼,樣例是
中的。
上面的代碼如果想要跑通,情確保C++版本至少是C++17,因?yàn)槠渲杏玫搅?string_view 以及更智能的自動(dòng)類型推導(dǎo)(如果低于這個(gè)版本會(huì)導(dǎo)致unexpected需要指定明確的error類型)。
函數(shù)式的接口
and_then:傳入一個(gè)回調(diào),在沒有錯(cuò)誤的時(shí)候調(diào)用,該回調(diào)的返回值是新的 expected 值(可以控制err)。如果有錯(cuò)誤返回原 expected 值。
or_else:傳入一個(gè)回調(diào),在有錯(cuò)誤的時(shí)候調(diào)用,該回調(diào)的返回值是新的 expected 值(可以控制err),并且回調(diào)的參數(shù)是對(duì)應(yīng)的錯(cuò)誤類型。如果沒有錯(cuò)誤返回原 expected 值。
transform/map:transform 是C++23標(biāo)準(zhǔn)中規(guī)定的接口,而該第三方庫作者又實(shí)現(xiàn)了一個(gè)名為map的接口,這兩者效果是一致的。傳入一個(gè)回調(diào),在沒有錯(cuò)誤的時(shí)候調(diào)用,回調(diào)的參數(shù)和返回值都不牽扯 expected 值,只是作值的變換,所以無法控制新的 expected 的 err 值。如果有錯(cuò)誤則返回原 expected 值。
transform_error/map_error:同上,但回調(diào)的調(diào)用時(shí)機(jī)和參數(shù)于 or_else 相同,但是需要注意的是,回調(diào)的返回值并不具備任何效用,也就是說如果 transform_error 中的回調(diào)被調(diào)用,那么返回的仍然是原本包含錯(cuò)誤信息的 expected 值。
簡單示例如下: