分享

c++标准库——错误和异常处理 - 闻道有先后

 verychen 2010-12-02

C++标准库的设计不是同构的,它包含的组建和模块有着不同风格的设计和实现。而对于错误和异常的处理就是这种差异的一个很明显的表现地方。一些库,诸如string类,支持详尽的错误处理,它尽可能详尽地检查可能出现的问题,并且当错误发生时抛出异常。而另一些部分,诸如STL(标准模板库),关注速度胜过安全性,它们内部就很少检查逻辑性错误,只有发生运行时错误时才抛出异常。

(1)标准异常类
所有的语言本身或者标准库支持的异常类都派生自exception根类,这些标准的异常类可以分为三组:
A、语言本身支持的异常类
这类异常作为c++语言本身的一部分。这些异常在下面操作失败时被抛出:
-》当全局操作符new失败时,bad_alloc异常类被抛出。除非使用了nothrow版本的new操作符。
-》当操作符dynamic_cast针对一个引用进行运行时类型转换失败时,bad_cast异常类被抛出。
-》当应用操作符typeid到一个zero值或者null指针时,bad_typeid异常类被抛出。
-》当出现一个非预期的异常时,会调用unexpected()函数抛出一个bad_exception异常类。但一个函数抛出一个不在其异常抛出列表中说明的异常时,就会自动调用unexcepted()函数。
例如:
class E1;
class E2;
void f() throw(E1) //exception specification:这里列出的就是异常抛出列表说明
{
    ...
    throw E1();     //允许,在异常列表中有说明
    ...
    throw E2();     //不被允许,没有在异常列表中说明。自动调用unexcepted(),最终调用terminate()结束程序
}
这就是说,如果函数抛出了没有在异常说明列表中包含的异常,就会引发unexcepted()函数,如果bad_exception也没有在异常说明列表中包含,那么unexcepted()就会通常调用terminate()来结束程序。而如果bad_exception异常类在函数开头的异常说明类表中包含,那么unexcepted()函数就不调用terminate(),而是抛出bad_exception异常类。
class E1;
class E2;
void f() throw(E1, std::bad_exception)
{
    ...
    throw E1();     //允许,在异常说明列表中包含
    ...
    throw E2();     //不被允许,没有在异常说明列表中包含。自动调用unexcepted(),然后抛出bad_exception异常
}

B、针对C++标准库的异常类
针对C++标准库的异常类通常派生自logic_error异常类。这些逻辑错误在理论上是可以通过改进程序来避免的。例如对函数参数执行根多的检查等。
C++标准库为逻辑错误提供了如下的异常类:
-》当检测到无效参数时,抛出invalid_argument异常类。例如当一个bitset(bits数组)被初始化为字符而不是0或1时。
-》当检测到对一段内存区域试图超越其最大长度进行某种操作时,抛出length_error异常类。例如对一个字符串数据追加太多字符擦好超出了字符串的最大长度。
-》当检测到一个参数值不在其预期的合理范围内,抛出out_of_range异常类。例如针对一个数字使用了一个错误的索引值。
-》当检测到一个domain错误时,抛出domain_error异常类。
-》对于标准库中的I/O操作部分,提供了一个特殊的异常类ios_base::failure。但I/O流因为一个错误或者文件结束符而改变它的状态时,抛出该异常类。

C、超出程序错误之外的异常类
运行时错误很多时候超出了程序控制的范围,往往不容易避免。这些运行时异常通常派生自runtime_error异常类,C++标准库为运行时错误提供了如下的异常类:
-》当发生一个内部运算的范围错误时,抛出range_error异常。
-》当发生一个运算溢出的错误时,抛出overflow_error异常。
-》当发生一个运算underflow错误时,抛出underflow_error异常。

这些标准异常类对应的文件如下:
exception & bad_exception : <exception>
bad_alloc : <new>
bad_cast & bad_typeid : <typeinfo>
ios_base::failure : <ios>
all other exception classes : <stdexcept>

(2)异常类的成员
当用catch字句捕获一个异常时,如果系统对这些异常进行处理,那么就需要了解这些标准异常类的接口。而对于所有的c++标准异常类,只有一个有用的接口:what(), 返回一个描述异常额外信息的以null结束的字符串。
namespace std 
{
    class exception 

    {
        public:
            virtual const char * what() const throw();
            ...
    };
}
而剩余的其他接口是为了创建(create)、复制(copy)、赋值(assign)和销毁(destroy)这些异常对象的。
那么要打印一个捕获的异常类的信息的典型代码片段如下:
try
{
    ...
}
catch (const exception & error)
{
    //print error message
    cerr << error.what() << endl;
    ...

 

(3)如何自己抛出c++异常类
当你用c++语言来完成自己的程序或者库时,也可以设计从程序的某处抛出c++的标准异常类。所以的标准异常类都接收一个string类型的参数,这个string会通过what()接口获取到。例如loggic_error异常类的定义如下:
namespace std
{
    class logic_error : public exception
    {
        public:
            explicit logic_error(const string & whatString);
    };
}
其他的标准类的定义也类似。但是,并不是所有的标准异常类在用户代码中都可以使用,可以在用户代码中使用的标准异常类有:logic_error和它的派生类;runtime_error和它的派生类;还有ios_base::failure。而根类exception和bad_alloc,bad_cast,bad_typeid,bad_exception这些标准异常类不能在用户代码中抛出。

使用标准异常类的方法非常简单:
string s;     //descripe the exception
...
throw out_of_range(s);     //throw the exception
而char *到string类型也可以隐式被转换,所以也可以直接使用下面形式:
throw out_of_range("exception description string");

(4)从标准异常类派生新的异常类
从标准异常类派生自己的异常类也是很容易的,关键一步就是保证what()接口可以正常工作。
我们自己的异常类可以直接派生自根类exception,这种情况下我们要重载what()接口,实现自己的what()接口。
namespace myLib
{
    class myException : public std::exception
    {
    ...
    public:
        myException(...)
        {
            ...
        }

        virtual const char * what() const throw() 
        {
            //implementation of what()
            ...
        }
        ...
    };
}

如果我们不希望自己实现what()接口,那么也可以派生自一个已经实现了what()接口的标准异常类,实例如下:
namespace myLib
{
    class myException : public std::out_of_range
    {
        ...
        public:
            myException(const string & whatString) : out_of_range(whatString)
            {
                ...
            }
            ...
    };
}

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多