Boost.Exception:给 C++ 异常附加错误上下文
C++ 异常对象携带的信息在构造时就固定了:throw my_error("failed") 抛出之后,中途的调用层想补充「是保存配置时失败的」这类上下文,标准异常没有手段。Boost.Exception 解决的就是这个问题:让异常在抛出点和任意一层 catch 点都能附加键值形式的信息,最后统一输出完整诊断。本文用一个完整示例把核心用法走一遍。
核心部件
三样东西构成这套机制:
boost::exception基类:异常类继承它之后获得携带附加信息的能力,定义在boost/exception/exception.hpp;boost::error_info<Tag, Type>:定义「信息键」的类型。第一个模板参数是一个标签结构体,仅用于在类型系统里区分不同的键;第二个参数是所存值的类型;operator<<与diagnostic_information:前者在抛出点或 catch 点往异常上附加信息,后者把异常连同所有附加信息格式化成一段可读文本。
相关类和函数分散在多个头文件里,示例统一包含 boost/exception/all.hpp。
完整示例
场景:一个分配内存的函数在失败时抛异常,中间层在转发前补一句「保存配置数据失败了」,最外层统一处理并打印诊断。
cpp
#include <boost/exception/all.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/shared_array.hpp>
#include <exception>
#include <string>
#include <iostream>
// 定义两个信息键:tag 结构体只用来在类型上区分,值都是字符串
typedef boost::error_info<struct tag_errmsg, std::string> errmsg_info;
typedef boost::error_info<struct tag_handle, std::string> errmsg_handle;
class allocation_failed : public boost::exception, public std::exception
{
public:
allocation_failed(std::size_t size)
: what_("allocation of " + boost::lexical_cast<std::string>(size) + " bytes failed")
{
}
virtual const char *what() const throw()
{
return what_.c_str();
}
private:
std::string what_;
};
boost::shared_array<char> allocate(std::size_t size)
{
if (size > 1000)
// 抛出宏会自动附加函数名、文件名、行号
BOOST_THROW_EXCEPTION(allocation_failed(size));
return boost::shared_array<char>(new char[size]);
}
void save_configuration_data()
{
try
{
boost::shared_array<char> a = allocate(2000);
// saving configuration data ...
}
catch (boost::exception &e)
{
// 中间层补充上下文后原样上抛
e << errmsg_info("saving configuration data failed");
throw;
}
}
void handle_eptr(std::exception_ptr eptr)
{
if (eptr == nullptr) return;
try
{
std::rethrow_exception(eptr); // 按原类型重新抛出
}
catch (allocation_failed &e)
{
std::cerr << "Caught allocation_failed.\n"
<< boost::diagnostic_information(e);
}
catch (const std::exception &e)
{
std::cerr << "Caught exception \"" << e.what() << "\"\n";
}
}
int main()
{
std::exception_ptr eptr;
try
{
save_configuration_data();
}
catch (boost::exception &e)
{
e << errmsg_handle("Handle by handle_eptr"); // 最外层再补一条
eptr = std::current_exception(); // 捕获为异常指针
}
handle_eptr(eptr); // 统一异常处理
} 运行输出:
Caught allocation_failed.
boost_e.cpp(32): Throw in function boost::shared_array<char> allocate(std::size_t)
Dynamic exception type: boost::wrapexcept<allocation_failed>
std::exception::what: allocation of 2000 bytes failed
[tag_errmsg*] = saving configuration data failed
[tag_handle*] = Handle by handle_eptr 逐层拆解
- 抛出点:
BOOST_THROW_EXCEPTION宏除了抛出异常,还自动把函数名、文件名、行号作为附加信息记进去——输出里的Throw in function ...一行即来自这里; - 中间层:
catch (boost::exception &e)捕获后用e << errmsg_info(...)追加一条信息,再空throw;原样上抛,异常对象上已附加的信息全部保留; - 最外层:
std::current_exception()把当前异常捕获成std::exception_ptr,交给统一的处理函数;std::rethrow_exception恢复异常的原始类型,进入对应的 catch 分支; - 输出:
diagnostic_information(e)把异常动态类型、what()内容、抛出位置和所有 error_info 键值一次性输出。只想取某一条时,可以用boost::get_error_info<errmsg_info>(e),返回指向值的智能指针,判空后解引用使用。
细节与边界
- 继承关系:示例中的异常类同时继承
boost::exception和std::exception——继承前者获得附加信息的能力,继承后者保证能被通用的catch (const std::exception &)捕获、what()可用。只继承std::exception也可以:BOOST_THROW_EXCEPTION会通过enable_error_info机制为这类类型注入附加能力,同时继承两者的写法最直观; - catch 必须用引用:
catch (boost::exception &e)按引用捕获才能修改异常对象、附加信息后继续传递;按值捕获会切掉附加信息所在的派生部分; - 与标准库的关系:
std::exception_ptr、std::current_exception、std::rethrow_exception是 C++11 标准设施,解决「把异常存起来、换个地方再抛」的传递问题;而「给异常附加键值信息」在标准库里没有直接等价物。Boost.Exception 补上的是后一块,两者可以并用。
小结
Boost.Exception 的价值集中在一点:异常的上下文可以沿途累积,而不必在抛出时一次凑齐。实践上三条建议:抛出统一走 BOOST_THROW_EXCEPTION,自动带上位置信息;中间层 catch 到就补业务上下文再上抛;最外层用 diagnostic_information 统一输出,配合日志系统定位问题。