Boost中的Exception介绍

本文介绍了一种利用Boost库改进C++异常处理的方法,通过将异常与异常数据分离,解决了异常抛出时信息不足的问题。这种方法避免了修改函数接口,提高了代码的可维护性和异常处理的准确性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

动机


 

在我们的项目中,可能会定义各种不同的异常,往往会遇到这种问题。比如:需要捕捉一个读取文件失败的异常 

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的异常,可以使得异常与异常数据分离,使得程序正常流程与异常流程不再有更多的依赖。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值