[Boost.Leaf]C++11轻量级错误框架

Boost.Leaf是一个专为C++11设计的轻量级错误处理库,提供单头文件、无依赖、嵌入式开发友好的解决方案。它支持静态错误处理,效率恒定,不依赖堆栈深度,且能与异常机制结合。库中的关键功能包括result类型、错误传播、try_handle_some和try_handle_all等机制,方便开发者编写高效且易于维护的错误处理代码。
摘要由CSDN通过智能技术生成

摘要

Boost.Leaf是一个给C++11搞的轻量级错误处理库。

  • 单头文件,无依赖
  • 嵌入式开发生成代码小
  • 没有动态内存分配,即使错误信息很大
  • 出没出错效率一样
  • 处理错误时间恒定,与调用堆栈深度无关
  • 可选异常机制

支持

获取

许可证:Boost Software License - Version 1.0
包含于:Boost 1.75
源码:GitHub
单个头文件:leaf.hpp

ℹ️LEAF不依赖Boost和其它库

教程

什么是一个失败呢?。。。

报告错误

enum class err1 { e1, e2, e3 };

leaf::result<T> f()
{
  ....
  if( error_detected )
    return leaf::new_error( err1::e1 ); // 传递任何类型的错误对象

  // 返回T
}

检查错误

leaf::result<U> g()
{
  leaf::result<T> r = f();
  if( !r )
    return r.error();

  T const & v = r.value();
  // 使用v产出U
}

💡r.error()兼容任何类型的leaf::result

if语句可以通过BOOST_LEAF_AUTO简写:

leaf::result<U> g()
{
  BOOST_LEAF_AUTO(v, f()); // Bail out on error

  // 使用v产出U
}

BOOST_LEAF_AUTO不能用于void结果,这种情况下我们使用BOOST_LEAF_CHECK

leaf::result<void> f();

leaf::result<int> g()
{
  BOOST_LEAF_CHECK(f()); // 发出错误
  return 42;
}

在支持__GNUC__的编译器中,BOOST_LEAF_CHECK可以利用Statement Exprs (Using the GNU Compiler Collection (GCC))的特性,使得非void的结果在表达式中使用:

leaf::result<int> f();

float g(int x);

leaf::result<float> t()
{
  return g( BOOST_LEAF_CHECK(f()) );
}

下面含义相同:

leaf::result<float> t()
{
  BOOST_LEAF_AUTO(x, f());
  return g(x);
}

错误处理

我们需要使用特殊语法来定义要访问的错误对象。我们举例若干方法处理err1错误:

leaf::result<U> r = leaf::try_handle_some(

  []() -> leaf::result<U>
  {
    BOOST_LEAF_AUTO(v1, f1());
    BOOST_LEAF_AUTO(v2, f2());

    return g(v1, v2);
  },

  []( err1 e ) -> leaf::result<U>
  {
    if( e == err1::e1 )
      .... // 处理err1::e1错误
    else
      .... // 处理其他err1错误
  } );

try_handle_some第一个lambda参数首先执行,它试图创建结果U,但它可能会失败。

第二个lambda参数是一个错误处理程序:如果第一个lambda执行失败并且产生err1错误,则它被调用。错误对象被存储在栈上,在try_handle_some函数中(LEAF根据错误处理程序的参数分配存储空间)。传给try_handle_some的错误处理程序可重新返回leaf::result<U>,且允许错误。

错误处理程序也可以指定只接受特定值:

leaf::result<U> r = leaf::try_handle_some(

  []() -> leaf::result<U>
  {
    BOOST_LEAF_AUTO(v1, f1());
    BOOST_LEAF_AUTO(v2, f2());

    return g(v1. v2);
  },

  []( leaf::match<err1, err1::e1, err1::e3> ) -> leaf::result<U>
  {
    // 处理err::e1或err1::e3错误
  },

  []( err1 e ) -> leaf::result<U>
  {
    // 处理其它err1错误
  } );

LEAF依序对比错误处理程序,根据错误对象,调用第一个它可以提供参数的错误处理程序,上面的程序中:

  1. 第一个错误处理程序通过leaf::match指定仅当错误包含err1类型且值为err1::e1err1::e3时执行
  2. 否则,如果错误包含err1类型,则调用第二个错误处理程序,不论其值如何
  3. 否则leaf::try_handle_some执行失败

错误处理程序也可以选择不处理当前错误:

leaf::result<U> r = leaf::try_handle_some(

  []() -> leaf::result<U>
  {
    BOOST_LEAF_AUTO(v1, f1());
    BOOST_LEAF_AUTO(v2, f2());

    return g(v1. v2);
  },

  []( err1 e, leaf::error_info const & ei ) -> leaf::result<U>
  {
    if( <<condition>> )
      return valid_U;
    else
      return ei.error();
  } );

错误处理程序可以使用一个leaf::error_info const &类型的参数来访问正在处理的错误的常规信息;这种情况下,我们可以使用成员函数error获取当前错误的唯一error_id;我们可以用它来初始化leaf::result以快速地将错误传递到try_handle_some之外。

💡如果我们想发出一个新的错误(而不是传递当前错误),我们可以通过leaf::new_error返回新的错误。

如果我们想确保所有错误都被处理,我们可以使用leaf::try_handle_all

U r = leaf::try_handle_all(

  []() -> leaf::result<U>
  {
    BOOST_LEAF_AUTO(v1, f1());
    BOOST_LEAF_AUTO(v2, f2());

    return g(v1. v2);
  },

  []( leaf::match<err1, err1::e1> ) -> U
  {
    // 处理err::e1错误
  },

  []( err1 e ) -> U
  {
    // 处理其他err1错误
  },

  []() -> U
  {
    // 处理其它任何错误
  } );

leaf::try_handle_all函数在编译期强制要求提供一个不带参数的错误处理程序(以处理任何错误)。而且,所有错误处理程序都被迫返回有效的U而不是leaf::result<U>,因此leaf::try_handle_all保证成功。

处理不同类型的错误

我们自然可以提供不同的错误处理程序处理不同的错误类型:

enum class err1 { e1, e2, e3 };
enum class err2 { e1, e2 };

....

leaf::result<U> r = leaf::try_handle_some(

  []() -> leaf::result<U>
  {
    BOOST_LEAF_AUTO(v1, f1());
    BOOST_LEAF_AUTO(v2, f2());

    return g(v1, v2);
  },

  []( err1 e ) -> leaf::result<U>
  {
    // 处理err1类型错误
  },

  []( err2 e ) -> leaf::result<U>
  {
    // 处理err2类型错误
  } );

我们仍按序对比错误处理程序:

  1. 如果错误包含err1类型,则执行第一个错误处理程序
  2. 否则,如果错误包含err2类型,则执行第二个错误处理程序
  3. 否则,leaf:try_handle_some失败

处理多个错误对象

leaf::new_error函数可以接受多个错误对象,例如绑定一个文件名:

enum class io_error { open_error, read_error, write_error };

struct e_file_name { std::string value; }

leaf::result<File> open_file( char const * name )
{
  ....
  if( open_failed )
    return leaf::new_error(io_error::open_error, e_file_name {name});
  ....
}

类似地,错误处理程序可以接受多个错误对象作为参数:

leaf::result<U> r = leaf::try_handle_some(

  []() -> leaf::result<U>
  {
    BOOST_LEAF_AUTO(f, open_file(fn));
    ....
  },

  []( io_error ec, e_file_name fn ) -> leaf::result<U>
  {
    // 处理IO错误,并且文件名可用
  },

  []( io_error ec ) -> leaf::result<U>
  {
    // 处理IO错误,文件名不可用
  } );

重复一次,错误处理程序被依次考虑:

  1. 当错误包含io_error并且包含e_file_name对象时,第一个错误处理程序被执行
  2. 否则,当错误包含io_error对象时,第二个错误处理程序被执行
  3. 否则,leaf::try_handle_some失败

还有一种方式是提供一个e_file_name指针类型的参数:

leaf::result<U> r = leaf::try_handle_some(

  []() -> leaf::result<U>
  {
    BOOST_LEAF_AUTO(f, open_file(fn));
    ....
  },

  []( io_error ec, e_file_name const * fn ) -> leaf::result<U>
  {
    if( fn )
      .... // 处理IO错误,并且文件名可用
    else
      .... // 处理IO错误,文件名不可用
  } );

错误处理程序不会因不包含指针类型指向的错误类型的对象而被跳过;此时,LEAF直接传递nullptr

💡错误处理程序可以接受值、(const)引用或(const)指针类型参数;使用引用或指针时,如果修改错误对象并且不处理错误,则该状态会被保留并向上传播。

增加错误

假如我们有函数parse_line可能出现io_errorparse_error

enum class io_error { open_error, read_error, write_error };

enum class parse_error { bad_syntax, bad_range };

leaf::result<int> parse_line( FILE * f );

leaf::on_error函数可以自动关联额外的错误对象到当前正在发生的错误:

struct e_line { int value; };

leaf::result<void> process_file( FILE * f )
{
  for( int current_line = 1; current_line != 10; ++current_line )
  {
    auto load = leaf::on_error( e_line {current_line} );

    BOOST_LEAF_AUTO(v, parse_line(f));

    // 使用v
  }
}

process_file不处理错误,除非出现问题时附加current_lineon_error返回的对象包含一份e_line{current_line}的副本,如果parse_line成功,则e_line对象被忽略,如果失败,则e_line对象会被自动附加到错误中。

上面的错误可以这样处理:

leaf::result<void> r = leaf::try_handle_some(

  [&]() -> leaf::result<void>
  {
    BOOST_LEAF_CHECK( process_file(f) );
  },

  []( parse_error e, e_line current_line  )
  {
    std::cerr << "Parse error at line " << current_line.value << std::endl;
  },

  []( io_error e, e_line current_line )
  {
    std::cerr << "I/O error at line " << current_line.value << std::endl;
  },

  []( io_error e )
  {
    std::cerr << "I/O error" << std::endl;
  } );

下面是一样的,不过可能简单些:

leaf::result<void> r = leaf::try_handle_some(

  []() -> leaf::result<void>
  {
    BOOST_LEAF_CHECK( process_file(f) );
  },

  []( parse_error e, e_line current_line )
  {
    std::cerr << "Parse error at line " << current_line.value << std::endl;
  },

  []( io_error e, e_line const * current_line )
  {
    std::cerr << "Parse error";
    if( current_line )
      std::cerr << " at line " << current_line->value;
    std::cerr << std::endl;
  } );

异常处理

抛异常咋办?莫担心try_handle_sometry_handle_all都会捕获异常,并且能够将它们传递给任何兼容的错误处理程序:

leaf::result<void> r = leaf::try_handle_some(

  []() -> leaf::result<void>
  {
    BOOST_LEAF_CHECK( process_file(f) );
  },

  []( std::bad_alloc const & )
  {
    std::cerr << "Out of memory!" << std::endl;
  },

  []( parse_error e, e_line l )
  {
    std::cerr << "Parse error at line " << l.value << std::endl;
  },

  []( io_error e, e_line const * l )
  {
    std::cerr << "Parse error";
    if( l )
      std::cerr << " at line " << l.value;
    std::cerr << std::endl;
  } );

上面的程序中,我们仅仅是添加了一个接受std::bad_alloc参数的错误处理程序,事情好像就如预期那般工作:LEAF会正确分发错误,不论错误来自于leaf::result还是来自于异常。

当然,如果我们显示使用异常处理,我们就不需要leaf::result了,此时我们用leaf::try_catch

leaf::try_catch(

  []
  {
    process_file(f);
  },

  []( std::bad_alloc const & )
  {
    std::cerr << "Out of memory!" << std::endl;
  },

  []( parse_error e, e_line l )
  {
    std::cerr << "Parse error at line " << l.value << std::endl;
  },

  []( io_error e, e_line const * l )
  {
    std::cerr << "Parse error";
    if( l )
      std::cerr << " at line " << l.value;
    std::cerr << std::endl;
  } );

注意,我们不需要修改错误处理程序!但这是咋工作的呢?process_file抛出了啥?

LEAF使用了一个新的异常处理技术,它不使用异常类型继承关系来对错误进行分类,也不在异常中携带数据。回想一下,我们通过leaf::result传递错误时,我们会调用leaf::new_error函数返回结果,直接传递若干错误对象到当前错误处理范围:

enum class err1 { e1, e2, e3 };
enum class err2 { e1, e2 };

....

leaf::result<T> f()
{
  ....
  if( error_detected )
    return leaf::new_error(err1::e1, err2::e2);

  // 创建并返回T
}

使用异常处理时,我们这样:

enum class err1 { e1, e2, e3 };
enum class err2 { e1, e2 };

T f()
{
  if( error_detected )
    leaf::throw_exception(err1::e1, err2::e2);

  // 创建并返回T
}

leaf::throw_exception函数传递错误就像leaf::new_error一样,然后抛出一个继承自std::exception类型的对象。在这种方式中,错误类型不重要了:leaf::try_catch捕获所有异常,然后使用常规的LEAF错误处理程序选择过程。

如果我们想要使用传统方式抛出一个继承自std::exception的异常,我们可以简单地传递其作为第一个参数到leaf::throw_exception函数:

leaf::throw_exception(std::runtime_error("Error!"), err1::e1, err2::e2);

这种情况下,返回地对象将继承自std::runtime_error而不是std::exception

并且,leaf::on_error仍旧可以工作。我们使用异常重写了process_file函数:

int parse_line( FILE * f ); // 抛出

struct e_line { int value; };

void process_file( FILE * f )
{
  for( int current_line = 1; current_line != 10; ++current_line )
  {
    auto load = leaf::on_error( e_line {current_line} );
    int v = parse_line(f);

    // 使用v
  }
}

使用外部结果类型

TODO


Binding Error Handlers in a std::tuple

Transporting Error Objects Between Threads

Classification of Failures

Converting Exceptions to result

Using error_monitor to Report Arbitrary Errors from C-callbacks

Diagnostic Information

Working with std::error_code, std::error_condition

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值