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)
{
...
}
...
};
}