C++ Boost Exception超详细讲解

C/C++
266
0
0
2023-06-23

Boost.Exception 库提供了一种新的异常类型 boost::exception,它允许您在抛出异常后将数据添加到异常中。此类型在 boost/exception/exception.hpp 中定义。由于 Boost.Exception 将其类和函数分布在多个头文件中,以下示例访问主头文件 boost/exception/all.hpp 以避免一个接一个地包含头文件。

Boost.Exception 支持 C++11 标准的机制,该机制将异常从一个线程传输到另一个线程。 boost::exception_ptr 类似于 std::exception_ptr。但是,Boost.Exception 并不能完全替代标准库中的头文件异常。例如,Boost.Exception 缺少对 std::nested_exception 类型的嵌套异常的支持。

注意

要使用 Visual C++ 2013 编译本章中的示例,请删除关键字 noexcept。此版本的 Microsoft 编译器尚不支持 noexcept。

示例 56.1。使用 boost::exception

#include <boost/exception/all.hpp>
#include <exception>
#include <new>
#include <string>
#include <algorithm>
#include <limits>
#include <iostream>
typedef boost::error_info<struct tag_errmsg, std::string> errmsg_info;
struct allocation_failed : public boost::exception, public std::exception
{
  const char *what() const noexcept { return "allocation failed"; }
};
char *allocate_memory(std::size_t size)
{
  char *c = new (std::nothrow) char[size];
  if (!c)
    throw allocation_failed{};
  return c;
}
char *write_lots_of_zeros()
{
  try
  {
    char *c = allocate_memory(std::numeric_limits<std::size_t>::max());
    std::fill_n(c, std::numeric_limits<std::size_t>::max(), 0);
    return c;
  }
  catch (boost::exception &e)
  {
    e << errmsg_info{"writing lots of zeros failed"};
    throw;
  }
}
int main()
{
  try
  {
    char *c = write_lots_of_zeros();
    delete[] c;
  }
  catch (boost::exception &e)
  {
    std::cerr << boost::diagnostic_information(e);
  }
}

Example56.1

示例 56.1 调用函数 write_lots_of_zeros(),该函数又调用 allocate_memory()。 allocate_memory() 动态分配内存。该函数将 std::nothrow 传递给 new 并检查返回值是否为 0。如果内存分配失败,则会抛出 allocation_failed 类型的异常。如果 new 分配内存失败,allocation_failed 替换默认抛出的异常 std::bad_alloc 。

write_lots_of_zeros() 调用 allocate_memory() 以尝试分配最大可能大小的内存块。这是在 std::numeric_limits 的 max() 的帮助下完成的。该示例故意尝试分配那么多内存以使分配失败。

allocation_failed 源自 boost::exception 和 std::exception。不需要从 std::exception 派生类。 allocation_failed 也可以派生自不同类层次结构的类,以便将其嵌入现有框架中。虽然示例 56.1 使用标准定义的类层次结构,但仅从 boost::exception 派生 allocation_failed 就足够了。

如果捕获到 allocation_failed 类型的异常,allocate_memory() 必须是异常的来源,因为它是唯一抛出此类异常的函数。在有许多函数调用 allocate_memory() 的程序中,知道异常的类型不再足以有效地调试程序。在这些情况下,了解哪个函数试图分配比 allocate_memory() 所能提供的更多的内存会有所帮助。

挑战在于 allocate_memory() 没有任何附加信息,例如调用者姓名,可以添加到异常中。 allocate_memory() 无法丰富异常。这只能在调用上下文中完成。

使用 Boost.Exception,可以随时将数据添加到异常中。您只需为需要添加的每一位数据定义一个基于 boost::error_info 的类型。

boost::error_info 是一个需要两个参数的模板。第一个参数是一个标签,用于唯一标识新创建的类型。这通常是具有唯一名称的结构。第二个参数是指存储在异常中的值的类型。示例 56.1 定义了一个新类型 errmsg_info——通过结构 tag_errmsg 唯一标识——它存储一个 std::string 类型的字符串。

在 write_lots_of_zeros() 的捕获处理程序中,errmsg_info 用于创建一个用字符串“写入大量零失败”初始化的对象。然后使用 operator<< 将该对象添加到 boost::exception 类型的异常中。然后异常被重新抛出。

现在,异常不仅仅表示内存分配失败。它还表示,当程序试图在函数 write_lots_of_zeros() 中写入大量零时,内存分配失败。知道哪个函数称为 allocate_memory() 可以更轻松地调试较大的程序。

要从异常中检索所有可用数据,可以在 main() 的捕获处理程序中调用函数 boost::diagnostic_information()。 boost::diagnostic_information() 为传递给它的每个异常调用成员函数 what() 并访问存储在异常中的所有附加数据。 boost::diagnostic_information() 返回一个 std::string 类型的字符串,例如,它可以写入标准错误。

当使用 Visual C++ 2013 编译时,示例 56.1 将显示以下消息:

Throw location unknown (consider using BOOST_THROW_EXCEPTION)
Dynamic exception type: struct allocation_failed
std::exception::what: allocation failed
[struct tag_errmsg *] = writing lots of zeros failed

该消息包含异常类型、从 what() 检索到的错误消息以及描述,包括结构名称。

boost::diagnostic_information() 在运行时检查给定异常是否源自 std::exception。 what() 只有在这种情况下才会被调用。

抛出 allocation_failed 类型异常的函数名称未知。

Boost.Exception 提供了一个宏来抛出异常,该异常不仅包含函数名,还包含文件名和行号等附加数据。

示例 56.2。更多数据与 BOOST_THROW_EXCEPTION

#include <boost/exception/all.hpp>
#include <exception>
#include <new>
#include <string>
#include <algorithm>
#include <limits>
#include <iostream>
typedef boost::error_info<struct tag_errmsg, std::string> errmsg_info;
struct allocation_failed : public std::exception
{
  const char *what() const noexcept { return "allocation failed"; }
};
char *allocate_memory(std::size_t size)
{
  char *c = new (std::nothrow) char[size];
  if (!c)
    BOOST_THROW_EXCEPTION(allocation_failed{});
  return c;
}
char *write_lots_of_zeros()
{
  try
  {
    char *c = allocate_memory(std::numeric_limits<std::size_t>::max());
    std::fill_n(c, std::numeric_limits<std::size_t>::max(), 0);
    return c;
  }
  catch (boost::exception &e)
  {
    e << errmsg_info{"writing lots of zeros failed"};
    throw;
  }
}
int main()
{
  try
  {
    char *c = write_lots_of_zeros();
    delete[] c;
  }
  catch (boost::exception &e)
  {
    std::cerr << boost::diagnostic_information(e);
  }
}

使用宏 BOOST_THROW_EXCEPTION 代替 throw,函数名、文件名和行号等数据会自动添加到异常中。但这仅在编译器支持附加数据的宏时才有效。虽然 __FILE__ 和 __LINE__ 等宏自 C++98 以来就已标准化,但获取当前函数名称的宏 __func__ 仅在 C++11 中成为标准。由于许多编译器在 C++11 之前提供了这样的宏,BOOST_THROW_EXCEPTION 会尝试识别底层编译器并使用相应的宏(如果存在)。

使用 Visual C++ 2013 编译,示例 56.2 显示以下消息:

main.cpp(20): Throw in function char *__cdecl allocate_memory(unsigned int)
Dynamic exception type: class boost::exception_detail::clone_impl<struct boost::exception_detail::error_info_injector<struct allocation_failed> >
std::exception::what: allocation failed
[struct tag_errmsg *] = writing lots of zeros failed

在示例 56.2 中,allocation_failed 不再派生自 boost::exception。 BOOST_THROW_EXCEPTION 访问函数 boost::enable_error_info(),它标识异常是否源自 boost::exception。如果不是,它会创建一个从指定类型和 boost::exception 派生的新异常类型。这就是为什么上面显示的消息包含与 allocation_failed 不同的异常类型。

示例 56.3。使用 boost::get_error_info() 有选择地访问数据

#include <boost/exception/all.hpp>
#include <exception>
#include <new>
#include <string>
#include <algorithm>
#include <limits>
#include <iostream>
typedef boost::error_info<struct tag_errmsg, std::string> errmsg_info;
struct allocation_failed : public std::exception
{
  const char *what() const noexcept { return "allocation failed"; }
};
char *allocate_memory(std::size_t size)
{
  char *c = new (std::nothrow) char[size];
  if (!c)
    BOOST_THROW_EXCEPTION(allocation_failed{});
  return c;
}
char *write_lots_of_zeros()
{
  try
  {
    char *c = allocate_memory(std::numeric_limits<std::size_t>::max());
    std::fill_n(c, std::numeric_limits<std::size_t>::max(), 0);
    return c;
  }
  catch (boost::exception &e)
  {
    e << errmsg_info{"writing lots of zeros failed"};
    throw;
  }
}
int main()
{
  try
  {
    char *c = write_lots_of_zeros();
    delete[] c;
  }
  catch (boost::exception &e)
  {
    std::cerr << *boost::get_error_info<errmsg_info>(e);
  }
}

Example56.3

示例 56.3 没有使用 boost::diagnostic_information(),它使用 boost::get_error_info() 直接访问 errmsg_info 类型的错误消息。因为 boost::get_error_info() 返回类型为 boost::shared_ptr 的智能指针,所以 operator* 用于获取错误消息。如果传递给 boost::get_error_info() 的参数不是 boost::exception 类型,则返回空指针。如果宏 BOOST_THROW_EXCEPTION 始终用于抛出异常,则异常将始终从 boost::exception 派生——在这种情况下无需检查返回的智能指针是否为 null。