C++使用expected实现优雅的错误处理

 更新时间:2023年06月13日 10:29:08   作者:L_B__  
C++ 中提供了很多中方式进行错误处理。无论是通过抛异常还是通过错误码,标准库都提供相应的调用,今天本文为大家介绍的是使用expected进行错误处理,感兴趣的可以了解一下

使用expected进行错误处理

C++ 中提供了很多中方式进行错误处理。无论是通过抛异常还是通过错误码,标准库都提供相应的调用。

  • 通过 try catch 以抛出异常的方式进行处理,这种方式很多人会觉得看起来可读性很差(包括我),并且由于缺少异常规约(某些异常必须捕获),容易出现 bug,而且异常的传递很多时候可能伴随动态分配内存,这是一笔不小的开销。
  • 通过 error_code 作为返回值判断,这种方式虽然看似简单易用,但是由于 C++ 中并未对 error_code 作过多的规范,使用起来并不方便,很多时候还是倾向于自定义一些枚举作为自己的 error_code,但是由于缺少多返回值的语法(当然如果C++版本比较高可用使用tuple以及解构的语法实现),如果把错误码作为返回值,那么原本的数据返回就只能函数参数传递引用的形式返回了。当然,如果不考虑函数返回值的具体错误信息,可以使用 C++17 的 optional 。

由于 optional 无法包含具体的错误信息,expected 横空出世,在 C++23 开始纳入标准。如果你的C++版本较低,可以使用第三方开源的 expected 库:github.com/TartanLlama/expected

下面我会以一个例子把第三方库中的 expected 库的使用方式介绍给大家。

expected 使用实例

由于该第三方库是 head-only 的,所以你只需要进到GitHub仓库把对应的头文件复制过来,便可引入使用。

下面是示例代码,样例是 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 以及更智能的自动类型推导(如果低于这个帮会导致unexpected需要指定明确的error类型)。

函数式的接口

  • and_then:传入一个回调,在没有错误的时候调用,该回调的返回值是新的 expected 值(可以控制err)。如果有错误返回原 expected 值。
  • or_else:传入一个回调,在有错误的时候调用,该回调的返回值是新的 expected 值(可以控制err),并且回调的参数是对应的错误类型。如果没有错误返回原 expected 值。
  • transform/map:transform 是C++23标准中规定的接口,而该第三方库作者又实现了一个名为map的接口,这两者效果是一致的。传入一个回调,在没有错误的时候调用,回调的参数和返回值都不牵扯 expected 值,只是作值的变换,所以无法控制新的 expected 的 err 值。如果有错误则返回原 expected 值。
  • transform_error/map_error:同上,但回调的调用时机和参数于 or_else 相同,但是需要注意的是,回调的返回值并不具备任何效用,也就是说如果 transform_error 中的回调被调用,那么返回的仍然是原本包含错误信息的 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) {
                           //自己构造了一个错误
                           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";
                           }
                           //自己构造了一个错误
                           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;
    }
}

到此这篇关于C++使用expected实现优雅的错误处理的文章就介绍到这了,更多相关C++错误处理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Opencv实现读取摄像头和视频数据

    Opencv实现读取摄像头和视频数据

    这篇文章主要为大家详细介绍了Opencv实现读取摄像头和视频数据,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-01-01
  • 二维指针动态分配内存连续问题深入分析

    二维指针动态分配内存连续问题深入分析

    当我们定义一个二维指针时,如果需要存储相应的数据,就需要我们动态的分配内存,这时,有一点是需要注意的,分配内存的方法不同,内存的连续性也是不相同的
    2013-07-07
  • C++ 实现双向链表的实例

    C++ 实现双向链表的实例

    这篇文章主要介绍了C++ 实现双向链表的实例的相关资料,需要的朋友可以参考下
    2017-07-07
  • C语言修炼之路一朝函数思习得 模块思维世间生下篇

    C语言修炼之路一朝函数思习得 模块思维世间生下篇

    函数是一组一起执行一个任务的语句。每个 C 程序都至少有一个函数,即主函数 main() ,所有简单的程序都可以定义其他额外的函数
    2022-03-03
  • C语言代码详细描述顺序线性表

    C语言代码详细描述顺序线性表

    这篇文章主要用代码介绍了C语言线性表的顺序线性表,对于学习数据结构与算法的朋友很有参考借鉴价值,需要的朋友可以参考下
    2021-09-09
  • C语言中操作sqlserver数据库案例教程

    C语言中操作sqlserver数据库案例教程

    这篇文章主要介绍了C语言中操作sqlserver数据库案例教程,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-07-07
  • OpenCV视频流C++多线程处理方法详细分析

    OpenCV视频流C++多线程处理方法详细分析

    为OpenCV是搞计算机视觉必须要掌握的基础,这篇文章主要给大家介绍了关于OpenCV视频流多线程处理的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2022-11-11
  • 详解C语言之操作符

    详解C语言之操作符

    这篇文章主要以图文结合的方式为大家详细介绍了C语言的操作符知识,感兴趣的小伙伴们可以参考一下,希望能给你带来帮助
    2021-11-11
  • C++前缀树字典树的学习与模拟实现代码示例

    C++前缀树字典树的学习与模拟实现代码示例

    这篇文章主要介绍了C++前缀树字典树的学习与模拟实现代码示例,Trie又被称为前缀树、字典树,所以当然是一棵树,上面这棵Trie树包含的字符串集合是{in,inn,int,tea,ten,to},每个节点的编号是我们为了描述方便加上去的,需要的朋友可以参考下
    2023-07-07
  • C++继承的定义与注意事项

    C++继承的定义与注意事项

    这篇文章主要给大家介绍了关于C++继承的定义与注意事项的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-05-05

最新评论