C++使用expected實現(xiàn)優(yōu)雅的錯誤處理
使用expected進行錯誤處理
C++ 中提供了很多中方式進行錯誤處理。無論是通過拋異常還是通過錯誤碼,標(biāo)準庫都提供相應(yīng)的調(diào)用。
- 通過 try catch 以拋出異常的方式進行處理,這種方式很多人會覺得看起來可讀性很差(包括我),并且由于缺少異常規(guī)約(某些異常必須捕獲),容易出現(xiàn) bug,而且異常的傳遞很多時候可能伴隨動態(tài)分配內(nèi)存,這是一筆不小的開銷。
- 通過 error_code 作為返回值判斷,這種方式雖然看似簡單易用,但是由于 C++ 中并未對 error_code 作過多的規(guī)范,使用起來并不方便,很多時候還是傾向于自定義一些枚舉作為自己的 error_code,但是由于缺少多返回值的語法(當(dāng)然如果C++版本比較高可用使用tuple以及解構(gòu)的語法實現(xiàn)),如果把錯誤碼作為返回值,那么原本的數(shù)據(jù)返回就只能函數(shù)參數(shù)傳遞引用的形式返回了。當(dāng)然,如果不考慮函數(shù)返回值的具體錯誤信息,可以使用 C++17 的 optional 。
由于 optional 無法包含具體的錯誤信息,expected 橫空出世,在 C++23 開始納入標(biāo)準。如果你的C++版本較低,可以使用第三方開源的 expected 庫:github.com/TartanLlama/expected
下面我會以一個例子把第三方庫中的 expected 庫的使用方式介紹給大家。
expected 使用實例
由于該第三方庫是 head-only 的,所以你只需要進到GitHub倉庫把對應(yīng)的頭文件復(fù)制過來,便可引入使用。
下面是示例代碼,樣例是 cppreference 中的。
#include "expected.h"
#include <iomanip>
#include <iostream>
#include <string>
?
enum class parse_error
{
invalid_input,
overflow
};
?
tl::expected<double, parse_error> parse_number(std::string_view& str)
{
const char* begin = str.data();
char* end;
double retval = std::strtod(begin, &end);
?
if (begin == end)
return tl::unexpected(parse_error::invalid_input);
else if (std::isinf(retval))
return tl::unexpected(parse_error::overflow);
?
str.remove_prefix(end - begin);
return retval;
}
?
int main()
{
auto process = [](std::string_view str) {
std::cout << "str: " << std::quoted(str) << ", ";
if (const auto num = parse_number(str); num)
{
std::cout << "value: " << *num << '\n';
// If num did not have a value, dereferencing num
// would cause an undefined behavior, and
// num.value() would throw std::bad_expected_access.
// num.value_or(123) uses specified default value 123.
}
else if (num.error() == parse_error::invalid_input)
{
std::cout << "error: invalid input\n";
}
else if (num.error() == parse_error::overflow)
{
std::cout << "error: overflow\n";
}
else
{
std::cout << "unexpected!\n";// or invoke std::unreachable();
}
};
?
for (auto src : {"42", "42abc", "meow", "inf"})
process(src);
}
上面的代碼如果想要跑通,情確保C++版本至少是C++17,因為其中用到了 string_view 以及更智能的自動類型推導(dǎo)(如果低于這個幫會導(dǎo)致unexpected需要指定明確的error類型)。
函數(shù)式的接口
- and_then:傳入一個回調(diào),在沒有錯誤的時候調(diào)用,該回調(diào)的返回值是新的 expected 值(可以控制err)。如果有錯誤返回原 expected 值。
- or_else:傳入一個回調(diào),在有錯誤的時候調(diào)用,該回調(diào)的返回值是新的 expected 值(可以控制err),并且回調(diào)的參數(shù)是對應(yīng)的錯誤類型。如果沒有錯誤返回原 expected 值。
- transform/map:transform 是C++23標(biāo)準中規(guī)定的接口,而該第三方庫作者又實現(xiàn)了一個名為map的接口,這兩者效果是一致的。傳入一個回調(diào),在沒有錯誤的時候調(diào)用,回調(diào)的參數(shù)和返回值都不牽扯 expected 值,只是作值的變換,所以無法控制新的 expected 的 err 值。如果有錯誤則返回原 expected 值。
- transform_error/map_error:同上,但回調(diào)的調(diào)用時機和參數(shù)于 or_else 相同,但是需要注意的是,回調(diào)的返回值并不具備任何效用,也就是說如果 transform_error 中的回調(diào)被調(diào)用,那么返回的仍然是原本包含錯誤信息的 expected 值。
簡單示例如下:
#include "expected.h"
#include <iostream>
#include <string>
?
enum class parse_error
{
invalid_input,
overflow
};
?
tl::expected<double, parse_error> parse_number(std::string_view& str)
{
const char* begin = str.data();
char* end;
double retval = std::strtod(begin, &end);
?
if (begin == end)
return tl::unexpected(parse_error::invalid_input);
else if (std::isinf(retval))
return tl::unexpected(parse_error::overflow);
?
str.remove_prefix(end - begin);
return retval;
}
?
int main()
{
auto sv = std::string_view{"0"};
auto result = parse_number(sv)
.and_then([](double x) {
?return tl::expected<double, parse_error>(x + 1);
})
.map([](double x) {
?return x + 1;
})
.transform([](double x) {
?return x + 1;
});
if (result)
std::cout << *result << "\n";
auto result2 = parse_number(sv)
? .and_then([](double x) {
? //自己構(gòu)造了一個錯誤
? tl::expected<double, parse_error> ret = tl::unexpected<parse_error>(parse_error::invalid_input);
? return ret;
? })
? .or_else([](parse_error err) {
? if (err == parse_error::invalid_input)
? {
? std::cout << "invalid error\n";
? }
? //自己構(gòu)造了一個錯誤
? tl::expected<double, parse_error> ret = tl::unexpected<parse_error>(parse_error::overflow);
? return ret;
? })
? .transform_error([](parse_error err) {
? if (err == parse_error::overflow)
? {
? std::cout << "overflow error\n";
? }
? return 32432.4324;
? }).map([](double x){
? return x+1;
? });
if (result2)
{
std::cout << *result2;
}
}到此這篇關(guān)于C++使用expected實現(xiàn)優(yōu)雅的錯誤處理的文章就介紹到這了,更多相關(guān)C++錯誤處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
數(shù)據(jù)結(jié)構(gòu)之鏈式二叉樹詳解
所謂二叉樹遍歷 (Traversal) 是按照某種特定的規(guī)則,依次對二叉樹中的節(jié)點進行相應(yīng)的操作,并且每個節(jié)點只操作一次。本文通過代碼示例詳細介紹了C語言中的鏈式二叉樹,需要的朋友可以參考一下2023-04-04
淺談C語言中strcpy,strcmp,strlen,strcat函數(shù)原型
下面小編就為大家?guī)硪黄獪\談C語言中strcpy,strcmp,strlen,strcat函數(shù)原型。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-04-04
C字符串操作函數(shù)實現(xiàn)方法小結(jié)
這篇文章主要介紹了C字符串操作函數(shù)實現(xiàn)方法,實例總結(jié)了C語言字符串操作的相關(guān)技巧,非常具有實用價值,需要的朋友可以參考下2015-04-04
C++數(shù)據(jù)結(jié)構(gòu)AVL樹全面分析
今天的這一篇博客,我要跟大家介紹一顆樹——AVL樹,它也是一顆二叉搜索樹,它就是在二叉搜索樹中加了一個平衡因子的概念在里面,下面我就來和大家聊一聊這棵樹是個怎么樣的樹2021-10-10
C++設(shè)計模式編程中使用Bridge橋接模式的完全攻略
這篇文章主要介紹了C++設(shè)計模式編程中使用Bridge橋接模式的完全攻略,Bridge將抽象部分與它的實現(xiàn)部分分離,使它們都可以獨立地變化需要的朋友可以參考下2016-03-03
在C++中實現(xiàn)高效的數(shù)組原地輪轉(zhuǎn)的方法總結(jié)
在 C++ 中,可以通過多種方式實現(xiàn)數(shù)組的輪轉(zhuǎn)操作,以下是幾種常見的實現(xiàn)方法及其對應(yīng)的代碼示例,文中通過代碼示例介紹的非常詳細,具有一定的參考價值,需要的朋友可以參考下2025-04-04

