C++中的异常处理
一.异常处理概述
异常处理是一种允许两个独立开发的程序组件在程序执行期间遇到程序不正常的情况(称为异常exception) 时相互通信的机制。C++的异常处理机制被称为是不可恢复的(nonresumptive) 一旦异常被处理,程序的执行就不能够在异常被抛出的地方继续.
二.检查,抛出,捕获异常。
检查异常:try
抛出异常:throw
捕获异常:catch
如果你再看这篇文章的时候还不明白这3个关键字的用法,建议你好好复习一下,然后再继续阅读本文^_^。
如果一个程序没有为已被抛出的异常提供catch 子句该怎么办呢?异常不能够保持在未被处理的状态。异常对于一个程序非常重要,它表示程序不能够继续正常执行。如果没有找到处理代码程序就调用C++标准库中定义的函数terminate()。 terminate()的缺省行为是调用abort() 指示从程序非正常退出。
例如:
#include
using namespace std;
int main()
{
int a=0;
try
{
if(a==0) throw "1";
}
catch(int c) //注意:没有数据类型转换!
{
cout< }
return 0;
}
在vs.net平台运行该程序,将导致如下的错误:
主要的原因在于程序抛出char类型的异常,但是,只是提供了int类型的catch处理器。
在异常处理过程中也可能存在单个catch 子句不能完全处理异常的情况,在某些修正动作之后,catch 子句可能决定该异常必须由函数调用链中更上级的函数来处理。那么catch子句可以通过重新抛出(rethrow) 该异常把异常传递给函数调用链中更上级的另一个catch子句rethrow 表达式的形式为
throw;
被重新抛出的异常就是原来的异常对象。一般情况下如果一个异常对象需要被重新抛出,那么,在重新抛出之前它应该被做某种程度的修改。异常规范(exception specification )提供了一种方案,它能够随着函数声明列出该函数可能抛出的异常。它保证该函数不会抛出任何其他类型的异常。异常规范跟随在函数参数表之后,它用关键字throw 来指定。后面是用括号括起来的异常类型表。;例如:
class iStack {
public:
void pop( int &value ) throw(popOnEmpty);
void push( int value ) throw(pushOnFull);
private:
};
对于pop()的调用保证不会抛出任何popOnEmpty 类型之外的异常。类似地对于push()的调用保证不会抛出任何pushOnFull 类型之外的异常。
异常声明是函数接口的一部分,它必须在头文件中的函数声明上指定。异常规范是函数和程序余下部分之间的协议。它保证该函数不会抛出任何没有出现在其异常规范中的异常。如果函数声明指定了一个异常规范,则同一函数的重复声明必须指定同一类型的异常规范。同一函数的不同声明上的异常规范是不能累积的。
如果函数抛出了一个没有被列在异常规范中的异常会怎么样?程序只有在遇到某种不正常情况时异常才会被抛出。在编译时刻编译器不可能知道在执行时程序是否会遇到这些异常。因此一个函数的异常规范的违例只能在运行时刻才能被检测出来,如果函数抛出了一个没有被列在其异常规范中的异常,则系统调用C++标准库中定义的函数unexpected()。unexpected()的缺省行为是调用terminate()。 如果函数抛出了一个没有被列在其异常规范中的异常系统未必就会调用unexpected() 。如果该函数自己处理该异常那么一切都不会有问题。例如:
void recoup( int op1, int op2 ) throw(ExceptionType)
{
try {
// ...
throw string("we're in control");
}
// 处理抛出的异常
catch ( string ) {
// 做一些必要的工作
}
} // ok, unexpected()没有被调用
即便在函数recoup()中抛出string 类型的异常而且函数recoup()保证不会抛出ExceptionType 类型之外的其他异常。但是因为该异常在其逃离函数recoup()之前被处理了。所以系统不会由于该函数抛出string 类型的异常而调用函数unexpected()。函数异常规范的违例只有在运行时刻才能被检测到。如果一个表达式能够抛出一个不被规范允许的异常类型则编译器不会产生编译时刻错误。如果这个表达式不会被执行或者
它从没有抛出违反异常规范的那个异常,则该程序会像期望的那样运行。而且该函数异常规范从不会被违反。例如:
extern void doit( int, int ) throw(string, exceptionType);
void action ( int op1, int op2) throw(string) {
doit( op1, op2 ); // 没有编译错误
// ...
}
函数doit()可以抛出一个exceptionType 类型的异常它不是函数action()的异常规范所允许的。即使函数action()不允许这种类型的异常,该函数也能编译成功。编译器产生相应的代码以确保当违反异常规范的异常被抛出时调用运行库函数unexpected()。空的异常规范保证函数不会抛出任何异常。例如函数no_problem()保证不会抛出任何异常:
extern void no_problem() throw();
如果一个函数声明没有指定异常规范,则该函数可以抛出任何类型的异常。在被抛出的异常类型与异常规范中指定的类型之间不允许类型转换。例如:
int convert( int parm ) throw(string)
{
if ( somethingRather )
// 程序错误:
// convert() 不允许 const char* 型的异常
throw "help!";
}
在函数convert()中的throw 表达式抛出一个C 风格的字符串。由这个throw 表达式创建的异常对象的类型为const char*。 通常const char*型的表达式可以被转换成string 类型。但是异常规范不允许从被抛出的异常类型到异常规范指定的类型之问的转换。如果convert()抛出该异常,则调用函数unexpected() 。为了修正这种情况,可以如下修改throw 表达式显式地把表达式的值转换成string 类型:
throw string( "help!" );
三.C++标准库的异常类层次结构
C++标准库中的异常层次的根类被称为exception 这个类被定义在库的头文件 中。它是C++标准库函数抛出的所有异常的基类。exception 类的接口如下:
namespace std {
class exception {
public:
exception() throw();
exception( const exception & ) throw();
exception& operator=( const exception& ) throw();
virtual ~exception() throw();
virtual const char* what() const throw();
};
}
与C++标准库中定义的所有类一样,exception 类被放在名字空间std 中以防止污染程序中的全局名字空间。类定义中的前四个函数分别是缺省构造函数,拷贝构造函数,拷贝赋值操作符以及析构函数。因为这些成员函数都是公有成员函数所以任何程序都可以自由地创建拷贝和赋值异常对象。析构函数是一个虚拟函数这使得从exception 类派生的类的定义更加容易。what() 返回一个C 风格字符串。C 风格字符串的目的是为被抛出的异常提供某种文本描述。函数what()是一个虚拟函数,从exception类派生的类可以用自己的版本改写函数what() 以便更好地描述派生的异常对象。
除了根exception 类C++标准库还提供了一些类。它们可被用在我们所编写的程序中以报告程序的不正常情况。在这些预定义的类所反映的错误模型中,错误被分成两个大类:
逻辑错误(logic error) 和运行时刻错误(run-time error)
逻辑错误是那些由于程序的内部逻辑而导致的错误。辑错误是可以避免的,在程序开始执行之前能够被检测到。例如违反了逻辑的先决条件或者违反了类的不变性。两者都是逻辑错误。在C++标准库中定义的逻辑错误如下:
namespace std {
class logic_error : public exception {
public:
explicit logic_error( const string &what_arg );
};
class invalid_argument : public logic_error {
public:
explicit invalid_argument( const string &what_arg );
};
class out_of_range : public logic_error {
public:
explicit out_of_range( const string &what_arg );
};
class length_error : public logic_error {
public:
explicit length_error( const string &what_arg );
};
class domain_error : public logic_error {
public:
explicit domain_error( const string &what_arg );
};
}
如果函数接收到一个无效的实参那么就会抛出一个invalid_argument 异常。而如果函数接收到一个不在期望范围内的实参则它可以抛出一个out_of_range 异常。函数还可以通
过抛出一个length_error 异常来报告企图产生一个长度值超出最大允许值的对象。另外编译器可能会抛出一个domain_error 异常来报告域错误domain error。
与此相对,运行时刻错误是由于程序域之外的事件而引起的错误运行时刻错误只在程序执行时才是可检测的。在C++标准库中定义的运行时刻错误如下:
namespace std {
class runtime_error : public exception {
public:
explicit runtime_error( const string &what_arg );
};
class range_error : public runtime_error {
public:
explicit range_error( const string &what_arg );
};
class overflow_error : public runtime_error {
public:
explicit overflow_error( const string &what_arg );
};
class underflow_error : public runtime_error {
public:
一.异常处理概述
异常处理是一种允许两个独立开发的程序组件在程序执行期间遇到程序不正常的情况(称为异常exception) 时相互通信的机制。C++的异常处理机制被称为是不可恢复的(nonresumptive) 一旦异常被处理,程序的执行就不能够在异常被抛出的地方继续.
二.检查,抛出,捕获异常。
检查异常:try
抛出异常:throw
捕获异常:catch
如果你再看这篇文章的时候还不明白这3个关键字的用法,建议你好好复习一下,然后再继续阅读本文^_^。
如果一个程序没有为已被抛出的异常提供catch 子句该怎么办呢?异常不能够保持在未被处理的状态。异常对于一个程序非常重要,它表示程序不能够继续正常执行。如果没有找到处理代码程序就调用C++标准库中定义的函数terminate()。 terminate()的缺省行为是调用abort() 指示从程序非正常退出。
例如:
#include
using namespace std;
int main()
{
int a=0;
try
{
if(a==0) throw "1";
}
catch(int c) //注意:没有数据类型转换!
{
cout< }
return 0;
}
在vs.net平台运行该程序,将导致如下的错误:
主要的原因在于程序抛出char类型的异常,但是,只是提供了int类型的catch处理器。
在异常处理过程中也可能存在单个catch 子句不能完全处理异常的情况,在某些修正动作之后,catch 子句可能决定该异常必须由函数调用链中更上级的函数来处理。那么catch子句可以通过重新抛出(rethrow) 该异常把异常传递给函数调用链中更上级的另一个catch子句rethrow 表达式的形式为
throw;
被重新抛出的异常就是原来的异常对象。一般情况下如果一个异常对象需要被重新抛出,那么,在重新抛出之前它应该被做某种程度的修改。异常规范(exception specification )提供了一种方案,它能够随着函数声明列出该函数可能抛出的异常。它保证该函数不会抛出任何其他类型的异常。异常规范跟随在函数参数表之后,它用关键字throw 来指定。后面是用括号括起来的异常类型表。;例如:
class iStack {
public:
void pop( int &value ) throw(popOnEmpty);
void push( int value ) throw(pushOnFull);
private:
};
对于pop()的调用保证不会抛出任何popOnEmpty 类型之外的异常。类似地对于push()的调用保证不会抛出任何pushOnFull 类型之外的异常。
异常声明是函数接口的一部分,它必须在头文件中的函数声明上指定。异常规范是函数和程序余下部分之间的协议。它保证该函数不会抛出任何没有出现在其异常规范中的异常。如果函数声明指定了一个异常规范,则同一函数的重复声明必须指定同一类型的异常规范。同一函数的不同声明上的异常规范是不能累积的。
如果函数抛出了一个没有被列在异常规范中的异常会怎么样?程序只有在遇到某种不正常情况时异常才会被抛出。在编译时刻编译器不可能知道在执行时程序是否会遇到这些异常。因此一个函数的异常规范的违例只能在运行时刻才能被检测出来,如果函数抛出了一个没有被列在其异常规范中的异常,则系统调用C++标准库中定义的函数unexpected()。unexpected()的缺省行为是调用terminate()。 如果函数抛出了一个没有被列在其异常规范中的异常系统未必就会调用unexpected() 。如果该函数自己处理该异常那么一切都不会有问题。例如:
void recoup( int op1, int op2 ) throw(ExceptionType)
{
try {
// ...
throw string("we're in control");
}
// 处理抛出的异常
catch ( string ) {
// 做一些必要的工作
}
} // ok, unexpected()没有被调用
即便在函数recoup()中抛出string 类型的异常而且函数recoup()保证不会抛出ExceptionType 类型之外的其他异常。但是因为该异常在其逃离函数recoup()之前被处理了。所以系统不会由于该函数抛出string 类型的异常而调用函数unexpected()。函数异常规范的违例只有在运行时刻才能被检测到。如果一个表达式能够抛出一个不被规范允许的异常类型则编译器不会产生编译时刻错误。如果这个表达式不会被执行或者
它从没有抛出违反异常规范的那个异常,则该程序会像期望的那样运行。而且该函数异常规范从不会被违反。例如:
extern void doit( int, int ) throw(string, exceptionType);
void action ( int op1, int op2) throw(string) {
doit( op1, op2 ); // 没有编译错误
// ...
}
函数doit()可以抛出一个exceptionType 类型的异常它不是函数action()的异常规范所允许的。即使函数action()不允许这种类型的异常,该函数也能编译成功。编译器产生相应的代码以确保当违反异常规范的异常被抛出时调用运行库函数unexpected()。空的异常规范保证函数不会抛出任何异常。例如函数no_problem()保证不会抛出任何异常:
extern void no_problem() throw();
如果一个函数声明没有指定异常规范,则该函数可以抛出任何类型的异常。在被抛出的异常类型与异常规范中指定的类型之间不允许类型转换。例如:
int convert( int parm ) throw(string)
{
if ( somethingRather )
// 程序错误:
// convert() 不允许 const char* 型的异常
throw "help!";
}
在函数convert()中的throw 表达式抛出一个C 风格的字符串。由这个throw 表达式创建的异常对象的类型为const char*。 通常const char*型的表达式可以被转换成string 类型。但是异常规范不允许从被抛出的异常类型到异常规范指定的类型之问的转换。如果convert()抛出该异常,则调用函数unexpected() 。为了修正这种情况,可以如下修改throw 表达式显式地把表达式的值转换成string 类型:
throw string( "help!" );
三.C++标准库的异常类层次结构
C++标准库中的异常层次的根类被称为exception 这个类被定义在库的头文件 中。它是C++标准库函数抛出的所有异常的基类。exception 类的接口如下:
namespace std {
class exception {
public:
exception() throw();
exception( const exception & ) throw();
exception& operator=( const exception& ) throw();
virtual ~exception() throw();
virtual const char* what() const throw();
};
}
与C++标准库中定义的所有类一样,exception 类被放在名字空间std 中以防止污染程序中的全局名字空间。类定义中的前四个函数分别是缺省构造函数,拷贝构造函数,拷贝赋值操作符以及析构函数。因为这些成员函数都是公有成员函数所以任何程序都可以自由地创建拷贝和赋值异常对象。析构函数是一个虚拟函数这使得从exception 类派生的类的定义更加容易。what() 返回一个C 风格字符串。C 风格字符串的目的是为被抛出的异常提供某种文本描述。函数what()是一个虚拟函数,从exception类派生的类可以用自己的版本改写函数what() 以便更好地描述派生的异常对象。
除了根exception 类C++标准库还提供了一些类。它们可被用在我们所编写的程序中以报告程序的不正常情况。在这些预定义的类所反映的错误模型中,错误被分成两个大类:
逻辑错误(logic error) 和运行时刻错误(run-time error)
逻辑错误是那些由于程序的内部逻辑而导致的错误。辑错误是可以避免的,在程序开始执行之前能够被检测到。例如违反了逻辑的先决条件或者违反了类的不变性。两者都是逻辑错误。在C++标准库中定义的逻辑错误如下:
namespace std {
class logic_error : public exception {
public:
explicit logic_error( const string &what_arg );
};
class invalid_argument : public logic_error {
public:
explicit invalid_argument( const string &what_arg );
};
class out_of_range : public logic_error {
public:
explicit out_of_range( const string &what_arg );
};
class length_error : public logic_error {
public:
explicit length_error( const string &what_arg );
};
class domain_error : public logic_error {
public:
explicit domain_error( const string &what_arg );
};
}
如果函数接收到一个无效的实参那么就会抛出一个invalid_argument 异常。而如果函数接收到一个不在期望范围内的实参则它可以抛出一个out_of_range 异常。函数还可以通
过抛出一个length_error 异常来报告企图产生一个长度值超出最大允许值的对象。另外编译器可能会抛出一个domain_error 异常来报告域错误domain error。
与此相对,运行时刻错误是由于程序域之外的事件而引起的错误运行时刻错误只在程序执行时才是可检测的。在C++标准库中定义的运行时刻错误如下:
namespace std {
class runtime_error : public exception {
public:
explicit runtime_error( const string &what_arg );
};
class range_error : public runtime_error {
public:
explicit range_error( const string &what_arg );
};
class overflow_error : public runtime_error {
public:
explicit overflow_error( const string &what_arg );
};
class underflow_error : public runtime_error {
public: