动机
在我们的项目中,可能会定义各种不同的异常,往往会遇到这种问题。比如:需要捕捉一个读取文件失败的异常
catch( file_read_error & e ) { std::cerr << e.file_name(); } |
而我们的实现代码可能是这样的
void read_file( FILE * f ) { .... size_t nr=fread(buf,1,count,f); if( ferror(f) ) throw file_read_error(???); .... } |
注意file_read_error中的???,捕捉异常处需要我们显示文件名,在实际抛出异常处,我们却无法得到文件名作为参数,我们仅有的,只是一个文件句柄FILE* !
通常折中的解决方案是,增加一个函数参数,将文件名传入,以供异常处使用
void read_file( FILE * f, char const * name ) { .... size_t nr=fread(buf,1,count,f); if( ferror(f) ) throw file_read_error(name); .... } |
但是,这并不是一个好的解决方案。通常,修改函数接口可能会影响较大(若这是一个虚函数接口,那么影响会更加庞大),而函数接口修改的目的,仅仅是异常需要用特定的参数,这未免有点得不偿失。
让我们这样思考:在异常抛出与异常捕捉之间往往会跨过多个函数调用,而这中间可能会提供出我们所需要的信息,为何不充分利用这一点呢?来看看boost中是如何解决这种异常问题的。
BOOST的异常解决方案
在boost中把异常与异常数据分开处理,让我们看看实际代码
struct exception_base: virtual std::exception, virtual boost::exception { }; struct io_error: virtual exception_base { }; struct file_read_error: virtual io_error { }; (1) typedef boost::error_info<struct tag_errno_code,int> errno_code; (2) void read_file( FILE * f ) { .... size_t nr=fread(buf,1,count,f); if( ferror(f) ) throw file_read_error() << errno_code(errno); (3) .... } |
上面的代码有几点注意的地方:
1、在(1)处我们定义了异常,尤其注意的是这些异常非常简单,不含有任何成员函数和成员变量。限制是,必须继承自boost::exception。
2、在(2)处我们定义了异常数据,boost::error_info的第二个模板参数int,表明该异常数据为int类型,而tag_errno_code则是一种标识(毕竟int类型的异常数据可能还有其他含义)。
3、在(3)处我们通过使用<<操作符,将异常与异常数据进行绑定。
这里貌似并没有解决我们的文件名问题,不要着急,让我们看看更上层的代码,也就是调用read_file的地方
typedef boost::error_info<struct tag_file_name,std::string> file_name; (1) .... try { if( FILE * fp=fopen("foo.txt","rt") ) { shared_ptr<FILE> f(fp,fclose); .... read_file(fp); (2) do_something(); .... } else throw file_open_error() << errno_code(errno); } catch( boost::exception & e ) { e << file_name("foo.txt"); (3) throw; (4) } |
1、在(1)处我们定义了文件名的异常数据,可见它的数据类型为std::string,标识为tag_file_name
2、在(2)处就是我们的read_file函数,它可能会抛出file_read_error异常,记住它是继承自boost::exception的。
3、在(3)处我们将捕捉到的所有boost::exception异常都绑定上文件名,当然也包括了file_read_error异常。这样file_read_error异常现在已经绑定了errno和file_name2个异常数据。
4、在(4)处我们将异常重新抛出,供更上层的函数使用。
回到我们一开始的代码,捕捉异常之处,代码现在可能就是这样子:
catch( io_error & e ) { std::cerr << "I/O Error!/n"; if( std::string const * fn=get_error_info<file_name>(e) ) (1) std::cerr << "File name: " << *fn << "/n"; if( int const * c=get_error_info<errno_code>(e) ) std::cerr << "OS says: " << strerror(*c) << "/n"; } |
1、在(1)处我们通过使用get_error_info函数取出我们想要的异常数据,模板参数即是需要的异常数据的类型
我们也可以使用diagnostic_information函数取得详细的诊断信息:
catch( io_error & e ) diagnostic_information(e); } |
打印出来的诊断信息,可能是这样子的:
example_io.cpp(83): Throw in function class boost::shared_ptr<struct _iobuf> __cdecl my_fopen(const char *,const char *) Dynamic exception type: class boost::exception_detail::clone_impl<class fopen_error> std::exception::what: example_io error [struct tag_errno *] = 2, OS says "No such file or directory" [struct tag_file_name *] = tmp1.txt [struct tag_function *] = fopen [struct tag_open_mode *] = rb |
结论
使用boost的异常,可以使得异常与异常数据分离,使得程序正常流程与异常流程不再有更多的依赖。