exception:
c++98标准定义了标准异常类std::exception及一系列子类,是整个c++语言错误处理的基础.
boost.exception库针对标准库中异常类的缺陷进行了强化,提供<<操作符重载,可以向异常传入任意数据,有助于增加异常的信息和表达力。
#include <boost/exception/all.hpp>
using namespace boost;
标准库中的异常:
c++98标准中定义了一个异常基类std::exception和try/catch/throw异常处理机制,std::exception又派生出若干子类,用以描述不用种类的异常,如bad_alloc,bad_cast,out_of_range等等。共同构建了c++异常处理框架.
c++允许任何类型作为异常抛出,但在std::exception出现后,我们应该尽量使用它,因为std::exception提供了一个很有用的成员函数what(),可以返回异常所携带的信息,这比简单的抛出一个整数错误值或者字符串更好,更安全;
如果std::exception及其子类不能满足程序对异常处理的要求,也可以继承它,为它添加更多的异常诊断信息:
示例;
class my_exception : public std::exception //继承标准异常类
{
private:
int err_no; //错误码信息
public:
my_exception(const char* msg, int err): //构造函数
std::exception(msg), err_no(err){} //初始化父类和错误码信息
int get_err_no()
{ return err_no;}
};
这种解法还存在一个问题:很多时候当发生异常时不能获得有关异常的完全诊断信息,而标准库的异常类一旦被抛出,它就成为了一个“死”对象,程序失去了对它的控制能力,只能使用它或者在抛出一个新的异常。
c++98标准的异常处理机制还不够完美。
下面的exception指的是boost::exception类;
exception库提供了两个类,exception和error_info,它们是exception库的基础。
exception的类摘要如下;
class exception
{
protected:
exception();
exception(exception const &x);
~exception();
template<class E, class Tag, class T>
private:
friend E const & operator<<(E const &, error_info<Tag, T>const &);
};
template<class ErrorInfo, class E>
typename ErrorInfo::error_info::value_type * get_error_info(E &x);
exception类几乎没有公开的成员函数(但有大量用于内部实现的私有函数和变量),被保护的构造函数表明了它的设计意图:它是一个抽象类,处理它的子类,任何人都不能创建或者销毁它,这保证了exception不会被误用。
exception的重要能力在于其友元操作符<<,可以存储error_info对象信息,存入的信息可以用自由函数get_error_info<>()随时再取出来。这个函数返回一个存储数据的指针,如果exception里没有这种类型的信息则返回空指针;
注意:exception特意没有从std::exception继承;
error_info的类摘要如下:
class error_info
{
public:
typedef T value_type;
error_info(value_type const &v);
value_type & value();
};
error_info提供了向异常类型添加信息的通用解法,第一个模板类型参数Tag是一个标记,它通常是一个空类,仅用来标记error_info类,使它在模板实例化时生成不同的类型,第二个模板类型参数T是真正存储信息的数据,可以用成员函数value()访问。
向异常传递信息:
exception和error_info被设计为配合std::exception一起工作,自定义的异常类可以安全地从exception和std::exception多重继承,从而获得两者的能力.
因为exception被定义为抽象类,因此程序必须定义它的子类才能使用它,exception必须使用虚拟继承的方式,通常,继承完成后自定义异常类的实现就结束了:
示例:
struct my_exception:
virtual std::exception,
virtual boost::exception
{}; //空实现,不需要实现代码
接下来需要定义异常需要存储的信息--使用模板类error_info,用一个struct作为第一个模板参数来标记信息类型,再用第二个模板参数指定信息的数据类型;
下面的代码使用error_info定义了两个存储int和string的信息类:
typedef boost::error_info<struct tag_err_no, int> err_no;
typedef boost::error_info<struct tag_err_str,string>err_str;
当发生异常时,可以创建一个自定义异常类,并用<<操作符向它存储任意信息,这些信息可以再任何时候使用get_error_info()函数提取。
示例:
#include <boost/exception/all.hpp>
using namespace boost;
using namespace std;
struct my_exception:
virtual std::exception,
virtual boost::exception
{};
typedef boost::error_info<struct tag_err_no, int> err_no;
typedef boost::error_info<struct tag_err_str,string>err_str;
int main()
{
try
{
try
{
throw my_exception() << err_no(10);
}
catch (my_exception& e)
{
cout<<*get_error_info<err_no>(e)<<endl;
cout<< e.what()<<endl;
e<< err_str("other Info");
throw;
}
}
catch(my_exception& e)
{
cout<< *get_error_info<err_str>(e)<<endl;
}
system("pause");
return 0;
}
程序首先定义了一个异常类my_exception,然后使用typedef定义了两种异常信息,err_no和err_str,用int和string分别存储错误码和错误信息,mian()函数使用function-try块来捕获异常,它把整个函数都包含在try块中,可以更好地把异常处理代码与正常流程代码分离;
throw my_exception()语句创建了一个my_exception异常类的临时对象,并立刻使用<<向它传递了err_no对象,存入错误码10,随后,异常被catch块捕获,自由函数get_error_info<err_no>(e)可以获得异常内部保存的信息值的指针,所以需要解引用操作符*访问。
异常还可以被追加信息,同样使用操作符<<,最后在function-try的catch块部分,异常被最终处理,程序结束.
更进一步的用法:
由于从exception派生出的异常类定义非常简单,因此可以很容易建立起一个适合自己程序的,精细完整的异常体系,使每个异常类只对应一种错误类型,使每个异常类只对应一种错误类型,只有都使用虚继承,类体系可以任意复杂,充分表达错误的含义:
处理异常的另一个重要工作是定义错误信息类型,基本方法是使用typedef来具体化error_info模板类,这比较麻烦,特别是有大量信息类型的时候,因此exception库特意提供若干预先定义好的错误信息类,如同标准定义的logic_err等类型,使程序员用起来更轻松:
typedef error_info<struct errinfo_api_function_, char const *>
errinfo_api_function;
typedef error_info<struct errinfo_at_line, int>errinfo_at_line;
typedef error_info<struct errinfo_errno_, int> errinfo_errno;
typedef error_info<struct errinfo_file_handle_,weak_ptr<FILE>>
errinfo_file_handle;
typedef error_info<struct errinfo_file_name_, std::string> errinfo_file_name;
typedef error_info<struct errinfo_file_open_mode_,str::string>
errinfo_file_open_mode;
typedef error_info<struct errinfo_type_info_name, std::string>
errinfo_type_info_name;
可用于常见的调用api,行号,错误代码,文件handle,文件名等错误信息的处理。
例如:
try
{
my_exception() << errinfo_api_function("call api")
<< errorinfo_errno(101);
}
catch(boost::exception& e)
{
cout<< *get_error_info<errinfo_api_function>(e);
cout<< *get_error_info<errinfo_errno>(e);
}
另外,exception库还提供三个预定义错误信息类型,但命名规则略有不同:
typedef error_info<struct throw_function_, char const*> throw_function;
typedef error_info<struct throw_file_, char const*> throw_file;
typedef error_info<struct throw_line_, int> throw_line;
这三个错误信息类型主要用于存储源代码的信息,配合宏BOOST_CURRENT_FUNCTION,__FILE__和__LINE__使用,可以获得调用函数名,源文件名和源代码行号.
但如果这些预定义类不能满足要求,还有使用typedef,为了解决这个不大不小的麻烦,可以自定义个辅助宏DEFINE_ERROR_INFO,它可以方便快捷地实现error_info的定义:
#define DEFINE_ERROR_INFO(type, name) \
typedef boost::error_info<struct tag##name, type>name
宏DEFINE_ERROR_INFO接受两个参数,type是它要存储的类型,name是所需要的错误信息类型名,使用预处理命令##创建了error_info所需要的标签类,它的使用方法很简单,就像是声明一个变量:
DEFINE_ERROR_INFO(int, err_no);
在宏展开后它相当于:
typedef boost::error_info<struct tag_err_no, int> err_no;
如果担心tag_前缀太简单,可能会与其他类名发生冲突,也可以自己定制特殊的标记字符串,本身建立用内置的__FILE__和__LINE__宏,:
tag_##__FILE__##__LINE__##name
包装标准异常:
exception库提供一个模板函数enable_error_info<T>(T &e),其中T是标准异常类或者其他自定义类型,它可以包装类型T,产生一个从boost::exception和T派生的类,从而在不修改原异常处理体系的前提下获得boost::exception的所有好处,如果类型T已经是boost::exception的子类,那么enable_error_info将返回e的一个拷贝.
enable_error_info()通常用在程序中已经存在异常类的场合,对这些异常类的修改很困难,这时候enable_error_info()就可以包装原有的异常类,从而很容器地在不变动任何已有代码的基础上把boost::exception集成到原有异常体系中。
示例:
#include <boost/exception/all.hpp>
using namespace boost;
using namespace std;
struct my_err{}; //某个自定义的异常类,未使用boost::exception
int main()
{
try
{
//使用enable_error_info包装自定义异常
throw enable_error_info(my_err()) << errinfo_errno(10);
}
catch (boost::exception& e) //这里必须使用boost::exception来捕获
{
cout << *get_error_info<errinfo_errno> (e) <<endl;
}
system("pause");
return 0;
}
注意代码中catch的用法,enable_error_info()返回的对象是boost::exception 和my_err的子类,catch的参数可以是这两者中的任意一个,但如果要使用boost::exception所存储的信息,就必须用boost::exception来捕获异常.
enable_error_info()也可以包装标准库的异常:
throw enable_error_info(std::runtime_error("runtime"))
<< errinfo_at_line(__LINE__);//包装标准异常
使用函数抛出异常:
由于各种原因,程序中的异常并不能总是从boost::exception继承,必须使用enable_error_info()来包装。
exception库提供throw_exception()函数来简化enable_error_info()的调用,它可以代替原始的throw语句来抛出异常,会自动使用enable_error_info()来包装异常对象,而且支持线程安全,比直接使用throw更好,相当于:
throw (boost::enable_error_info(e))
从而确保抛出的异常是boost::exception的子类,可以追加异常信息,例如:
throw_exception(std::runtime_error("tuntime"))
在throw_exception()的基础上exception库又提供了一个非常有用的宏BOOST_THROW_EXCEPTION,它调用了boost::throw_exception()和enable_error_info(),因而可以接受任意的异常类型,同时又使用throw_function,throw_file和throw_line自动向异常添加了发生异常的函数名,文件名和行号等信息。
注意,throw_exception()函数和BOOST_THROW_EXCEPTION宏都 要求参数e必须是std::exception的子类.
如果确保在程序中总使用boost::exception,不会去定义配置宏BOOST_NO_EXCEPTIONS,那么可以修改Boost源代码,在<boost/throw_exception.hpp>里注释掉throw_exception_assert_compatibility(e)这条语句,以取消这个限制.
获取更多的调试信息:
exception提供了方便的存储信息能力,可以向它添加任意数量的信息,但当异常对象被用operator<<多次追加数据时,会导致它存储有大量的信息(BOOST_THROW_EXCEPTION就是个例子),如果还是采用自由函数get_error_info来逐项检索的话会很麻烦,这是需要另外一个函数,diagnostic_information().
diagnostic_information()可以输出异常包含的所有信息,如果异常是由宏BOOST_THROW_EXCEPTION抛出的,则可能相当多并且不是用户友好的,但对程序开发者可以提供很多诊断错误信息。
示例;
#include <boost/exception/all.hpp>
using namespace boost;
using namespace std;
struct my_err{}; //某个自定义的异常类,未使用boost::exception
int main()
{
try
{
//使用enable_error_info包装自定义异常
throw enable_error_info(my_err()) << errinfo_errno(101)
<< errinfo_api_function("fopen");
}
catch (boost::exception& e) //这里必须使用boost::exception来捕获
{
cout << diagnostic_information(e) <<endl;
}
try
{
BOOST_THROW_EXCEPTION(std::logic_error("logic"));//必须是标准异常
}
catch (boost::exception& e)
{
cout<< diagnostic_information(e)<<endl;
}
system("pause");
return 0;
}
高级议题:
对异常信息打包:
exception支持使用boost::tuple对异常信息进行组合打包,当异常信息类型很多有经常成组出现时可以简化抛出异常的编写:
例如:
typedef tuple<errinfo_api_function, errinfo_errno> err_group;
try
{
//使用enable_error_info包装自定义异常
throw enable_error_info(std::out_of_range("out"))
<< err_group("syslogd", 874);
}
catch(boost::exception&)
{
cout<< current_exception_diagnostic_information()<<endl;
}
类型转换:
模板函数current_exception_cast<E>()提供类似标准库的转型操作,它类似于current_exception_diagnostic_informationn(),只能在catch块内部使用,可以把异常对象转型为指向E类型的指针,如果异常对象无法转换成E*,则返回空指针。
示例:boost::exception转型为std::exception,前提是异常必须是std::exception的子类
catch( boost::exception&)
{
cout<< current_exception_cast<std::exception>()->what();
}
线程间传递异常:
exception库支持在线程间传递异常,这需要使用boost::exception的clone能力,使用enable_current_exception()包装异常对象或者使用throw_exception()都能够包装异常对象使之可以被clone.
当发生异常时,线程在需要catch块调用函数current_exception()得到当前异常对象的指针,exception_ptr对象,它指向异常对象的拷贝,是线程安全的,可以被多个线程同时拥有并发修改,rethrow_exception()可以重新抛出异常.
示例:
#include <boost/exception/all.hpp>
using namespace boost;
using namespace std;
void thread_work() //线程工作函数
{
throw_exception(std::exception("test"));
}
int main()
{
try
{
thread_work(); //启动一个线程,可能抛出异常
}
catch (...) //这里必须使用boost::exception来捕获
{
exception_ptr e = current_exception();
cout<<current_exception_diagnostic_information();
}
system("pause");
return 0;
}