2023年11月29日发(作者:)

boost之路⼗五错误处理

是⼀个定义了四个类的⼩型库,⽤以识别错误。 boost::system::error_code 是⼀个最基本的类,⽤于代表某个特定操作系统的异常。 由于

操作系统通常枚举异常,boost::system::error_code 中以变量的形式保存错误代码 int。 下⾯的例⼦说明了如何通过访问 类来

使⽤这个类。

#include

#include

#include

#include

int main()

{

boost::system::error_code ec;

std::string hostname = boost::asio::ip::host_name(ec);

std::cout << () << std::endl;

}

提供了独⽴的函数 boost::asio::ip::host_name() 可以返回正在执⾏的应⽤程序名。

boost::system::error_code 类型的⼀个对象可以作为单独的参数传递给 boost::asio::ip::host_name()。 如果当前的操作系统函数失败, 这个

参数包含相关的错误代码。 也可以通过调⽤ boost::asio::ip::host_name() ⽽不使⽤任何参数,以防⽌错误代码是⾮相关的。

事实上在Boost 1.36.0中 boost::asio::ip::host_name() 是有问题的,然⽽它可以当作⼀个很好的例⼦。 即使当前操作系统函数成功返回了

计算机名,这个函数它也可能返回⼀个错误代码。 由于在Boost 1.37.0中解决了这个问题,现在可以放⼼使⽤ boost::asio::ip::host_name()

了。

由于错误代码仅仅是⼀个数值,因此可以借助于 value() ⽅法得到它。 由于错误代码0通常意味着没有错误,其他的值的意义则依赖于操作

系统并且需要查看相关⼿册。

如果使⽤Boost 1.36.0, 并且⽤Visual Studio 2008在Windows XP环境下编译以上应⽤程序将不断产⽣错误代码14(没有⾜够的存储

空间以完成操作)。 即使函数 boost::asio::ip::host_name() 成功决定了计算机名,也会报出错误代码14。 事实上这是因为函数

boost::asio::ip::host_name() 的实现有问题。

除了 value() ⽅法之外, 类 boost::system::error_code 提供了⽅法 category()。 这个⽅法可返回⼀个在 中定义的⼆级对象:

boost::system::category

错误代码是简单的数值。 操作系统开发商,例如微软,可以保证系统错误代码的特异性。 对于任何开发商来说,在所有现有应⽤程序中保

持错误代码的独⼀⽆⼆是⼏乎不可能的。 他需要⼀个包含有所有软件开发者的错误代码中⼼数据库,以防⽌在不同的⽅案下重复使⽤相同

的代码。 当然这是不实际的。 这是错误分类表存在的缘由。

类型 boost::system::error_code 的错误代码总是属于可以使⽤ category() ⽅法获取的分类。 通过预定义的对象

boost::system::system_category

来表⽰操作系统的错误。

通过调⽤ category() ⽅法,可以返回预定义变量 的⼀个引⽤。 它允许获取关于分类的特定信息。 例如

boost::system::system_category

在使⽤的是 system 分类的情况下,通过使⽤ name() ⽅法将得到它的名字 system

#include

#include

#include

#include

int main()

{

boost::system::error_code ec;

std::string hostname = boost::asio::ip::host_name(ec);

std::cout << () << std::endl;

std::cout << ry().name() << std::endl;

}

通过错误代码和错误分类识别出的错误是独⼀⽆⼆的。 由于仅仅在错误分类中的错误代码是必须唯⼀的,程序员应当在希望定义某个特定

应⽤程序的错误代码时创建⼀个新的分类。 这使得任何错误代码都不会影响到其他开发者的错误代码。

#include

#include

#include

class application_category :

public boost::system::error_category

{

public:

const char *name() const { return "application"; }

std::string message(int ev) const { return "error message"; }

};

application_category cat;

int main()

{

boost::system::error_code ec(14, cat);

std::cout << () << std::endl;

std::cout << ry().name() << std::endl;

}

通过创建⼀个派⽣于 boost::system::error_category 的类以及实现作为新分类的所必须的接⼝的不同⽅法可以定义⼀个新的错误分类。 由于

#include

#include

#include

#include

int main()

{

boost::system::error_code ec;

std::string hostname = boost::asio::ip::host_name(ec);

boost::system::error_condition ecnd = t_error_condition();

std::cout << () << std::endl;

std::cout << ry().name() << std::endl;

}

boost::system::error_condition 的使⽤⽅法与 boost::system::error_code 类似。 对象boost::system::error_conditionvalue()category()

⽅法都可以像上⾯的例⼦中那样调⽤。

有或多或少两个相同的类的原因很简单:当类 boost::system::error_code 被当作当前平台的错误代码时, 类 boost::system::error_condition

可以被⽤作获取跨平台的错误代码。 通过调⽤ default_error_condition() ⽅法,可以把依赖于某个平台的的错误代码转换成

boost::system::error_condition 类型的跨平台的错误代码。

如果执⾏以上应⽤程序,它将显⽰数字12以及错误分类 GENERIC。 依赖于平台的错误代码14被转换成了跨平台的错误代码12。 借助于

boost::system::error_condition ,可以总是使⽤相同的数字表⽰错误,⽆视当前操作系统。 当Windows报出错误14时,其他操作系统可能

会对相同的错误报出错误代码25。 使⽤ boost::system::error_condition ,总是对这个错误报出错误代码12。

最后 提供了类 boost::system::system_error ,它派⽣于 std::runtime_error。 它可被⽤来传送发⽣在异常⾥类型为

boost::system::error_code 的错误代码。

#include

#include

#include

int main()

{

try

{

std::cout << boost::asio::ip::host_name() << std::endl;

}

catch (boost::system::system_error &e)

{

boost::system::error_code ec = ();

std::cerr << () << std::endl;

std::cerr << ry().name() << std::endl;

#include

这个例⼦在 main() 中调⽤了⼀个函数 save_configuration_data() ,它调回了 allocate()allocate() 函数动态分配内存,⽽它检查是否超过

某个限度。 这个限度在本例中被设定为1,000个字节。

如果 allocate() 被调⽤的值⼤于1,000,将会抛出 save_configuration_data() 函数⾥的相应异常。 正如注释中所标识的那样,这个函数把配

置数据被存储在动态分配的内存中。

事实上,这个例⼦的⽬的是通过抛出异常以⽰范 ion。 这个通过 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() 中并没有调⽤者名等信息,以把它加⼊到相关的异常中。

ion 提供了如下的解决⽅案:对于任何⼀个可以添加到异常中的信息,可以通过定义⼀个派⽣于 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编译会显⽰如下的信息:

Throw in function (unknown)

Dynamic exception type: class allocation_failed

std::exception::what: allocation of 2000 bytes failed

[struct tag_errmsg *] = saving configuration data failed

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

boost::diagnostic_information() 函数在运⾏时检查⼀个给定的异常是否派⽣于 std::exception。 只会在派⽣于 std::exception 的条件下调⽤

what() ⽅法。

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

ion 提供了⼀个⽤以抛出异常的宏,它包含了函数名,以及如⽂件名、⾏数的附加信息。

#include

通过使⽤宏 BOOST_THROW_EXCEPTION 替代 throw, 如函数名、⽂件名、⾏数之类的附加信息将⾃动被添加到异常中。但这仅仅在编

译器⽀持宏的情况下有效。 当通过C++标准定义 __FILE____LINE__ 之类的宏时,没有⽤于返回当前函数名的标准化的宏。 由于许多

编译器制造商提供这样的宏, BOOST_THROW_EXCEPTION 试图识别当前编译器,从⽽利⽤相对应的宏。 使⽤ Visual C++ 2008 编

译时,以上应⽤程序显⽰以下信息:

.(31): Throw in function class boost::shared_array __cdecl allocate(unsigned int)

Dynamic exception type: class boost::exception_detail::clone_impl >

std::exception::what: allocation of 2000 bytes failed

[struct tag_errmsg *] = saving configuration data failed

即使 allocation_failed 类不再派⽣于 boost::exception 代码的编译也不会产⽣错误。 BOOST_THROW_EXCEPTION 获取到⼀个能够动态

识别是否派⽣于 boost::exception 的函数 boost::enable_error_info()。 如果不是,他将⾃动建⽴⼀个派⽣于特定类和 boost::exception 的新

异常类型。 这个机制使得以上信息中不仅仅显⽰内存分配异常 allocation_failed

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

#include

这个例⼦并没有使⽤函数 boost::diagnostic_information() ⽽是使⽤ boost::get_error_info() 函数来直接获取错误信息的类型 errmsg_info

函数 boost::get_error_info() ⽤于返回 boost::shared_ptr 类型的智能指针。 如果传递的参数不是 boost::exception 类型的,返回的值将是相

应的空指针。 如果 BOOST_THROW_EXCEPTION 宏总是被⽤来抛出异常,派⽣于 boost::exception 的异常是可以得到保障的——在这些

情况下没有必要去检查返回的智能指针是否为空。