7-异常处理
Posted itzyjr
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了7-异常处理相关的知识,希望对你有一定的参考价值。
目录
如果代码出现问题,我们的职责便是通知用户,告诉他发生了什么事。我们以C++异常处理机制(exception handling facility)来完成通知任务。
7.1抛出异常(Throwing an Exception)
异常处理机制有两个主要成分:➊异常的鉴定与发出;➋异常的处理方式。
异常出现后,正常程序的执行便被暂停(suspended)。与此同时,异常处理机制开始搜索程序中有能力处理这一异常的地点。异常被处理完毕之后,程序的执行便会继续(resume),从异常处理点接着执行下去。
C++通过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);
}
所谓异常(exception)是某种对象。
最简单的异常对象可以设计为整数或字符串:
throw 42;
throw "panic: no buffer!";
大部分时候,被抛出的异常都属于特定的异常类。
例如,以下便是iterator_overflow class的定义:
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;
};
可以明确指出被抛出的对象名称:
if (_index > Triangular::_max_elems) {
iterator_overflow ex(_index, Triangular::_max_elems);
throw ex;
}
7.2捕获异常(Catching an Exception)
// 以下东西定义于其他地点......
extern void log_message(const char*);
extern string err_messages[];
extern ostream log_file;
bool some_function() {
bool status = true;
// ......假设我们抵达此处!
catch(int errno) {
log_message(err_messages[errno]);
status = false;
} catch(const char *str) {
log_message(str);
status = false;
} catch(iterator_overflow &iof) {
iof.what_happened(log_file);
status = false;
}
return status;
}
有时我们可能无法完成异常的完整处理。在记录信息之外,我们可以重新抛出(rethrow)异常,以寻求其他catch子句的协助,做进一步处理:
catch(iterator_overflow &iof) {
log_message(iof.what_happened());
throw;// 重新抛出异常,令另一个catch子句接手处理
}
可以捕获任何类型的异常,使用一网打尽(catch-all)的方式。只需在异常声明部分指定省略号(...
)即可:
catch(...) {
log_message("exception of unknown type");
// 清理(clean up)然后退出......
}
7.3提炼异常(Trying for an Exception)
以下函数试着在first和last所标示的范围内,寻找特定的elem。在此范围内进行迭代操作,可能会引发iterator_overflow异常。try...catch...
使用如下:
bool has_elem(Triangular_iterator first, Triangular_iterator last, int elem) {
bool status = true;
try {
while (first != last) {
if (*first == elem)
return status;
++first;
}
} catch(iterator_overflow &iof) {
log_message(iof.what_happened());
log_message("check if iterators address same container");
}
status = false;
return status;
}
如果“函数调用链”不断地被解开,一直回到了main()还是找不到合适的catch子句,会发生什么?C++规定,每个异常都应该被处理,因此,如果在main()内还是找不到合适的处理程序,便调用标准库提供的terminate()——其默认行为是中断整个程序的执行。
注意:C++异常和segment fault或bus error这类硬件异常不要混淆在一起。
面对任何一个被抛出的C++异常,你都可以在程序某处找到一个相应的throw表达式。有些深藏在标准库中。
7.4局部资源管理(Local Resource Management)
以下函数,开始要求分配相关资源,然后进行某个处理操作,函数结束前,这些资源会被释放。如下函数写法,犯了什么错误?
extern Mutex m;
void f() {
// 请求资源
int *p = new int;
m.acquire();
process(p);
// 释放资源
m.release();
delete p;
}
问题在于我们无法保证,函数执行之初所分配的资源最终一定会被释放掉。如果process()本身或process()内调用的函数抛出异常,那么process()调用操作之后的两条用以释放资源的语句便不会被执行。
解决方法是导入一个try
块,以及相应的catch
子句。这样便可捕获所有异常、释放所有资源、再将异常重新抛出:
void f() {
try {
// 和先前一样
} catch( ... ) {
m.release();
delete p;
throw;
}
}
用以释放资源的程序代码得出现两次,而且捕获异常、释放资源、重新抛出异常,这些操作会使异常处理程序(exception handler)的搜寻时间进一步延长。此外,程序代码本身也更复杂了。
我们希望写出更具防护性、更自动化的处理方式。在C++中这通常意味着定义一个专属的class。
C++引入了资源管理(resource management)的手法。对对象而言,初始化操作发生于constructor内,资源的请求亦应在constructor内完成。资源的释放则应该在destructor内完成。这样虽然无法将资源管理自动化,却可简化我们的程序:
#include <memory>
void f() {
auto_ptr<int> p(new int);
MutexLock ml(m);
process(p);
// p和ml的destructor会在此处被悄悄调用......
}
p和m1都是局部对象。如果process()执行无误,那么相应的destructor便会在函数结束前自动作用于p和ml身上。如果process()执行过程中有任何异常被抛出,会发生什么?
==在异常处理机制终结某个函数之前,C++保证,函数中的所有局部对象的destructor都会被调用。==本例中,不论是否有任何异常被抛出,p和ml的destructor保证被调用。
MutexLock class可以实现成下面这样:
class MutexLock {
public:
MutexLock(Mutex m): _lock(m) { lock.acquire(); }
~MutexLock() { lock.release(); }
private:
Mutex &_lock;
};
auto_ptr
是标准库提供的class template,它会自动删除通过new表达式分配的对象,例如先前例子中的p。使用它之前,必须包含相应的memory头文件#include <memory>
。
auto_ptr
将dereference运算符和arrow运算符予以重载,这使我们得以像使用一般指针一样地使用auto_ptr
对象。例如:
auto_ptr<string> aps(new string("vermeer"));
string *ps = new string("vermeer");
if ((aps->size() == ps-size()) && (*aps == *ps))
// ...
7.5标准异常(The Standard Exceptions)
vector<string>* init_text_vector(ifstream &infile) {
vector<string> *ptext = 0;
try {
ptext = new vector<string>;
// 打开file和file vector
} catch(bad_alloc) {
cerr << "ouch. heap memory exhausted!\\n";
// ......清理(clean up)并退出
}
return ptext;
}
以下语句:
ptext = new vector<string>;
会分配足够的内存,然后将vector< string>的默认构造函数(default constructor)应用于heap(堆)对象之上,然后再将对象地址设置给ptext。
注意:bad_alloc
是个class,不是一个object。
标准库定义了一套异常类体系(exception class hierarchy),其根部是名为exception
的抽象基类。exception声明有一个what()虚函数,会返回一个const char *,用以表示被抛出异常的文字描述。
bad_alloc派生自exception基类,它有自己的what()。
我们也可以将自己编写的iterator_overflow继承于exception基类之下。首先必须包含标准头文件exception,而且必须提供自己的what():
#include <exception>
class iterator_overflow : public exception {
public
iterator_overflow(int index, int max) : _index(index), _max(max) {}
int index() { return _index; }
int max() { return _max; }
// 重载exception::what(),a must!
const char* what() const;
private:
int _index;
int _max;
};
下面这个catch子句:
catch(const exception &ex) {
cerr << ex.what() << endl;
}
会捕获exception的所有派生类。当bad_alloc异常被抛出,它会打印"bad allocation"信息。当iterator_overflow异常抛出,它会打印"Internal error:current index 65 exceeds maximum bound:64"信息(因为what()行为如下段代码所示)。
以下便是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
class提供“内存内的输出操作”,输出到一个string对象上。当我们需要将多笔不同类型的数据格式化为字符串时,它尤其有用。
str()
会将“与ostringstream对象相呼应”的那个string对象返回。标准库what()返回一个const char*,而非一个string对象。c_str()
会返回我们所要的const char*。
相对应的,如果我们需要将非字符串数据(如整数值或内存地址)的字符串表示转换为其实际类型,istringstream
可派上用场。
以上是关于7-异常处理的主要内容,如果未能解决你的问题,请参考以下文章