• 15.3. Boost.Exception

    15.3. Boost.Exception

    Boost.Exception 库提供了一个新的异常类 boost::exception 允许给一个抛出的异常添加信息。 它被定义在文件 boost/exception/exception.hpp 中。 由于 Boost.Exception 中的类和函数分布在不同的头文件中, 下面的例子中将使用 boost/exception/all.hpp 以避免一个一个添加头文件。

    1. #include <boost/exception/all.hpp>
    2. #include <boost/lexical_cast.hpp>
    3. #include <boost/shared_array.hpp>
    4. #include <exception>
    5. #include <string>
    6. #include <iostream>
    7.  
    8. typedef boost::error_info<struct tag_errmsg, std::string> errmsg_info;
    9.  
    10. class allocation_failed :
    11. public boost::exception,
    12. public std::exception
    13. {
    14. public:
    15. allocation_failed(std::size_t size)
    16. : what_("allocation of " + boost::lexical_cast<std::string>(size) + " bytes failed")
    17. {
    18. }
    19.  
    20. virtual const char *what() const throw()
    21. {
    22. return what_.c_str();
    23. }
    24.  
    25. private:
    26. std::string what_;
    27. };
    28.  
    29. boost::shared_array<char> allocate(std::size_t size)
    30. {
    31. if (size > 1000)
    32. throw allocation_failed(size);
    33. return boost::shared_array<char>(new char[size]);
    34. }
    35.  
    36. void save_configuration_data()
    37. {
    38. try
    39. {
    40. boost::shared_array<char> a = allocate(2000);
    41. // saving configuration data ...
    42. }
    43. catch (boost::exception &e)
    44. {
    45. e << errmsg_info("saving configuration data failed");
    46. throw;
    47. }
    48. }
    49.  
    50. int main()
    51. {
    52. try
    53. {
    54. save_configuration_data();
    55. }
    56. catch (boost::exception &e)
    57. {
    58. std::cerr << boost::diagnostic_information(e);
    59. }
    60. }
    • 下载源代码

    这个例子在 main() 中调用了一个函数 save_configuration_data() ,它调回了 allocate()allocate() 函数动态分配内存,而它检查是否超过某个限度。 这个限度在本例中被设定为1,000个字节。

    如果 allocate() 被调用的值大于1,000,将会抛出 save_configuration_data() 函数里的相应异常。 正如注释中所标识的那样,这个函数把配置数据被存储在动态分配的内存中。

    事实上,这个例子的目的是通过抛出异常以示范 Boost.Exception。 这个通过 allocate() 抛出的异常是 allocation_failed 类型的,而且它同时继承了 boost::exceptionstd::exception

    当然,也不是一定要派生于 std::exception 异常的。 为了把它嵌入到现有的框架中,异常 allocation_failed 可以派生于其他类的层次结构。 当通过C++标准来定义以上例子的类层次结构的时候, 单独从 boost::exception 中派生出 allocation_failed 就足够了。

    当抛出 allocation_failed 类型的异常的时候,分配内存的大小是存储在异常中的,以缓解相应应用程序的调试。 如果想通过 allocate() 分配获取更多的内存空间,那么可以很容易发现导致异常的根本原因。

    如果仅仅通过一个函数(例子中的函数 save_configuration_data())来调用 allocate() ,这个信息足以找到问题的所在。 然而,在有许多函数调用 allocate() 以动态分配内存的更加复杂的应用程序中,这个信息不足以高效的调试应用程序。 在这些情况下,它最好能有助于找到哪个函数试图分配 allocate() 所能提供空间之外的内存。 向异常中添加更多的信息,在这些情况下,将非常有助于进程的调试。

    有挑战性的是,函数 allocate() 中并没有调用者名等信息,以把它加入到相关的异常中。

    Boost.Exception 提供了如下的解决方案:对于任何一个可以添加到异常中的信息,可以通过定义一个派生于 boost::error_info 的数据类型,来随时向这个异常添加信息。

    boost::error_info 是一个需要两个参数的模板,第一个参数叫做标签(tag),特定用来识别新建的数据类型。 通常是一个有特定名字的结构体。 第二个参数是与存储于异常中的数据类型信息相关的。

    这个应用程序定义了一个新的数据类型 errmsg_info,可以通过 tag_errmsg 结构来特异性的识别,它存储着一个 std::string 类型的字符串。

    save_configuration_data()catch 句柄中,通过获取 tag_errmsg 以创建一个对象,它通过字符串 "saving configuration data failed" 进行初始化,以便通过 operator<<() 操作符向异常 boost::exception 中加入更多信息。 然后这个异常被相应的重新抛出。

    现在,这个异常不仅包含有需要动态分配的内存大小,而且对于错误的描述被填入到 save_configuration_data() 函数中。 在调试时,这个描述显然很有帮助,因为可以很容易明白哪个函数试图分配更多的内存。

    为了从一个异常中获取所有可用信息,可以像例子中那样在 main()catch 句柄中使用函数 boost::diagnostic_information() 。 对于每个异常,函数 boost::diagnostic_information() 不仅调用 what() 而且获取所有附加信息存储到异常中。 返回一个可以在标准输出中写入的 std::string 字符串。

    以上程序通过Visual C++ 2008编译会显示如下的信息:

    1. Throw in function (unknown)
    2. Dynamic exception type: class allocation_failed
    3. std::exception::what: allocation of 2000 bytes failed
    4. [struct tag_errmsg *] = saving configuration data failed

    正如我们所看见的,数据包含了异常的数据类型,通过 what() 方法获取到错误信息,以及包括相应结构体名的描述。

    boost::diagnostic_information() 函数在运行时检查一个给定的异常是否派生于 std::exception。 只会在派生于 std::exception 的条件下调用 what() 方法。

    抛出异常类型 allocation_failed 的函数名会被指定为"unknown"(未知)信息。

    Boost.Exception 提供了一个用以抛出异常的宏,它包含了函数名,以及如文件名、行数的附加信息。

    1. #include <boost/exception/all.hpp>
    2. #include <boost/lexical_cast.hpp>
    3. #include <boost/shared_array.hpp>
    4. #include <exception>
    5. #include <string>
    6. #include <iostream>
    7.  
    8. typedef boost::error_info<struct tag_errmsg, std::string> errmsg_info;
    9.  
    10. class allocation_failed :
    11. public std::exception
    12. {
    13. public:
    14. allocation_failed(std::size_t size)
    15. : what_("allocation of " + boost::lexical_cast<std::string>(size) + " bytes failed")
    16. {
    17. }
    18.  
    19. virtual const char *what() const throw()
    20. {
    21. return what_.c_str();
    22. }
    23.  
    24. private:
    25. std::string what_;
    26. };
    27.  
    28. boost::shared_array<char> allocate(std::size_t size)
    29. {
    30. if (size > 1000)
    31. BOOST_THROW_EXCEPTION(allocation_failed(size));
    32. return boost::shared_array<char>(new char[size]);
    33. }
    34.  
    35. void save_configuration_data()
    36. {
    37. try
    38. {
    39. boost::shared_array<char> a = allocate(2000);
    40. // saving configuration data ...
    41. }
    42. catch (boost::exception &e)
    43. {
    44. e << errmsg_info("saving configuration data failed");
    45. throw;
    46. }
    47. }
    48.  
    49. int main()
    50. {
    51. try
    52. {
    53. save_configuration_data();
    54. }
    55. catch (boost::exception &e)
    56. {
    57. std::cerr << boost::diagnostic_information(e);
    58. }
    59. }
    • 下载源代码

    通过使用宏 BOOSTTHROWEXCEPTION 替代 throw, 如函数名、文件名、行数之类的附加信息将自动被添加到异常中。但这仅仅在编译器支持宏的情况下有效。 当通过C++标准定义 FILE__LINE 之类的宏时,没有用于返回当前函数名的标准化的宏。 由于许多编译器制造商提供这样的宏, BOOST_THROW_EXCEPTION 试图识别当前编译器,从而利用相对应的宏。 使用 Visual C++ 2008 编译时,以上应用程序显示以下信息:

    1. .\main.cpp(31): Throw in function class boost::shared_array<char> __cdecl allocate(unsigned int)
    2. Dynamic exception type: class boost::exception_detail::clone_impl<struct boost::exception_detail::error_info_injector<class allocation_failed> >
    3. std::exception::what: allocation of 2000 bytes failed
    4. [struct tag_errmsg *] = saving configuration data failed

    即使 allocation_failed 类不再派生于 boost::exception 代码的编译也不会产生错误。 BOOST_THROW_EXCEPTION 获取到一个能够动态识别是否派生于 boost::exception 的函数 boost::enable_error_info()。 如果不是,他将自动建立一个派生于特定类和 boost::exception 的新异常类型。 这个机制使得以上信息中不仅仅显示内存分配异常 allocation_failed

    最后,这个部分包含了一个例子,它选择性的获取了添加到异常中的信息。

    1. #include <boost/exception/all.hpp>
    2. #include <boost/lexical_cast.hpp>
    3. #include <boost/shared_array.hpp>
    4. #include <exception>
    5. #include <string>
    6. #include <iostream>
    7.  
    8. typedef boost::error_info<struct tag_errmsg, std::string> errmsg_info;
    9.  
    10. class allocation_failed :
    11. public std::exception
    12. {
    13. public:
    14. allocation_failed(std::size_t size)
    15. : what_("allocation of " + boost::lexical_cast<std::string>(size) + " bytes failed")
    16. {
    17. }
    18.  
    19. virtual const char *what() const throw()
    20. {
    21. return what_.c_str();
    22. }
    23.  
    24. private:
    25. std::string what_;
    26. };
    27.  
    28. boost::shared_array<char> allocate(std::size_t size)
    29. {
    30. if (size > 1000)
    31. BOOST_THROW_EXCEPTION(allocation_failed(size));
    32. return boost::shared_array<char>(new char[size]);
    33. }
    34.  
    35. void save_configuration_data()
    36. {
    37. try
    38. {
    39. boost::shared_array<char> a = allocate(2000);
    40. // saving configuration data ...
    41. }
    42. catch (boost::exception &e)
    43. {
    44. e << errmsg_info("saving configuration data failed");
    45. throw;
    46. }
    47. }
    48.  
    49. int main()
    50. {
    51. try
    52. {
    53. save_configuration_data();
    54. }
    55. catch (boost::exception &e)
    56. {
    57. std::cerr << *boost::get_error_info<errmsg_info>(e);
    58. }
    59. }
    • 下载源代码

    这个例子并没有使用函数 boost::diagnostic_information() 而是使用 boost::get_error_info() 函数来直接获取错误信息的类型 errmsg_info。 函数 boost::get_error_info() 用于返回 boost::shared_ptr 类型的智能指针。 如果传递的参数不是 boost::exception 类型的,返回的值将是相应的空指针。 如果 BOOST_THROW_EXCEPTION 宏总是被用来抛出异常,派生于 boost::exception 的异常是可以得到保障的——在这些情况下没有必要去检查返回的智能指针是否为空。