- 异常处理机制(exception handing facility):身为设计者我们不知道某个可能发生的问题对于整个程序的危害程度究竟如何,只有该函数的用户才知道问题的严重性。因此我们的职责就是通知用户,告诉他发生了什么事,异常处理机制便是用来完成通知任务的。
- 异常处理机制包括:异常的鉴定与发出 + 异常的处理方式。
- 异常出现后,正常程序的执行便被暂停(suspended),同时异常处理机制开始搜索程序中有能力处理这一异常的地点。异常被处理完毕后,程序的执行便会继续(resume),从异常处理点接着执行下去。
- 抛出(thrown)异常:
throw
表达式看起来像函数调用,异常是某种对象,大部分时候都属于特定的异常类(也许形成一个继承体系)。
inline void Triangular_iterator::
check_integrity()
{
if(_index >= Triangular::_max_elems)
throw iterator_overflow(_index, Triangular::_max_elems);
if(_index > Triangular::_elems.size())
Triangular::gen_elements(_index + 1);
}
//iterator_overflow类的定义
class iterator_overflow{
public:
iterator_overflow(int index, int max)
: _index(index), _max(max) {
}
int index() {
return _index;}
int max() {
return _max;}
void what_happened(ostream &os = cerr) {
os << "Internal error: current index "
<< _index << " exceeds maximum bound: "
<< _max;
}
private:
int _index;
int _max;
};
- 上述
throw
表达式会直接调用拥有两个参数的constructor。也可以明确指出被抛出对象的名称:
if(_index > Triangular::_max_elems)
{
iterator_overflow ex(_index, Triangular::_max_elems);
throw ex;
}
- 捕获(catch)异常:利用单条或一连串的
catch
子句来捕获。catch
子句由三部分组成:关键字catch
+ 小括号内的一个类型或对象 + 大括号内的一组语句(用来处理异常):
catch(iterator_overflow &iof)
{
iof.what_happened(log_file);
}
- 异常对象的类型会被拿来逐一地和每个
catch
子句比对。如果类型符合,那么该子句的内容便会被执行。在通过所有的catch子句后,由正常程序重新接手。
如上所示,通过
iof
这个异常对象,调用异常类中的成员函数what_happened()
.
- 当无法完成异常的完整处理时,或许需要重新抛出(rethrow)异常,以寻求其他catch子句的协助:
catch(iterator_overflow &iof)
{
log_message( iof.what_happened() );
//重新抛出异常,令另一个catch子句接手处理
throw;
}
重新抛出时,只需写下关键字
throw
即可,它只可能出现于catch
子句中,将捕获的异常对象再一次抛出,并由另一个类型吻合的catch子句接手处理。
- 如果我们想要捕获任何类型的异常,可以使用一网打尽(catch-all)的方式:
//在异常声明部分指定省略号
catch(...)
{
log_message( "exception of unknown type" );
//清理(clean up)然后退出...
}
- 提炼异常:
catch
子句应该和try
块相应而生。try
块以关键字try
作为开始,后接大括号内的一连串程序语句。catch
子句放在try
块末尾,表示如果try
块内有任何异常发生,便由接下来的catch
子句加以处理。 - 函数抛出异常后,异常处理机制便开始查看,异常由何处抛出,并判断是否位于
try
块内?如果是,就检验相应的catch
子句,看它是否具备处理此异常的能力,如果有,这个异常便被处理,而程序也就继续执行下去(从被执行的catch
子句之后的第一行语句开始)。 - 若不在
try
块内,则异常处理机制终究该函数的执行权,其剩余内容不会被执行。但异常处理机制会继续在该函数的调用端搜寻类型吻合的catch
子句。 - 如果函数调用链不断被解开,一直到
main()
还是找不到合适的catch
子句,C++规定,每个异常都应该被处理,于是调用标准库提供的terminate()
——其默认行为是中断整个程序的执行。 - 如果某一语句可能引发异常,而它不位于
try
块内,那么该异常一定不会在此函数内被捕获处理。 - 并非每个函数都必须处理每一个可能发生的异常。
- 当函数的
try
块发生异常,但并没有相应的catch
子句将它捕获,该函数便会被中断,由异常处理机制接管,沿着函数调用链一路回溯,搜寻符合条件的catch
子句。 - 面对任何一个被抛出的C++异常,你都可以在程序某处找到一个相应的
throw
表达式。 - 不能把C++异常和segmentation fault或是bus error这类硬件异常混淆在一起。
- 下列函数错误,无法保证函数执行之初所分配的资源最终一定会被释放掉。如果process()抛出异常,则其后两条释放资源的语句都不会被执行。
extern Mutex m;
void f()
{
//请求资源
int *p = new int;
m.acquire();
process(p);
//释放资源
m.release();
delete p;
}
- 可以加入try块和相应的catch子句,catch子句捕获所有异常、释放资源、再将异常重新抛出。(但用以释放资源的程序代码得出现两次(catch和catch后都要有))
- 资源管理(resource management):在初始化阶段即进行资源请求。
- 对对象而言,初始化操作发生于constructor内,资源的请求也应该在constructor内完成。资源的释放则应该在destructor内完成。
#include <memory>
void f()
{
auto_ptr<int> p(new int);
MutexLock ml(m);
process(p);
//p和ml的destructor会在此处被调用
}
- 在异常处理机制终结某个函数前,C++保证,函数中的所有局部对象的destructor都会被调用!
auto_ptr
是标准库提供的class template,它会自动删除通过new
表达式分配的对象,使用之前要包含头文件#include <memory>
,其中重载了derefrence(*
)运算符和arrow(->
)运算符,使得可以像使用一般指针一样使用auto_ptr
对象。
auto_ptr<string> aps(new string("hello"));
string *ps = new string("hello");
if((aps -> size() == ps -> size()) && (*aps == *ps))
//true
- 如果
new
表达式无法从程序的空闲空间分配到足够的内存,会抛出bad_alloc
异常对象。 - 如果要抑制不让
bad_alloc
异常被抛出,可以ptext = new (nothrow) vector<string>;
,这样如果new
操作失败,会返回0
。任何人使用ptext
之前都应该先检验它是否为0
. catch(bad_alloc)
并未声明出任何对象,只对捕获到的异常类型感兴趣,并没有打算在catch子句中实际操作该对象。- 标准库定义了一套异常类体系,根部为抽象基类
exception
。exception
声明有一个what()
虚函数,返回const char *
来表示被抛出异常的文字描述。#include <exception>
- 可以将自己编写的
iterator_overflow
融入标准的exception
类体系中,必须提供自己的what()
,因此可以被任何打算捕获抽象基类exception的程序所捕获。
//捕获exception的所有派生类
catch( const exception &ex)
{
cerr << ex.what() << endl;
}
- 以下便是
iterator_overflow
的what()
实现方式。
//运用ostringstream对象对输出信息进行格式化
#include <sstream>
#include <string>
const char* iterator_overflow::
what() const
{
ostringstream ex_msg;
static string msg;
//将输出信息写到内存中的 ostringstream对象之中
//将整数值转为字符串表示
ex_msg << "Internal error: current index "
<< _index << "exceeds maximum bound: "
<< _max;
//萃取出string对象
msg = ex_msg.str();
//萃取出const char*表达式
return msg.c_str();
}
ostringstream
类提供内存内的输出操作,输出到一个string
对象上。能自动将_index
、_max
这类数值对象转换为相应的字符串而不必考虑储存空间、转换算法等问题#include <sstream>
。ostringstream
所提供的一个member functionstr()
能将与对象对应的那个string对象返回。- string类提供的转换函数
c_str()
返回一个C-style字符串。 - iostream库也提供了相应的
istringstream
类。将非字符串数据的字符串表示转换为其实际类型。 ifstream
constructor接受的参数是const char*
而非string
。