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-异常处理的主要内容,如果未能解决你的问题,请参考以下文章

使用片段中的处理程序时出现非法状态异常

Java异常处理机制

vscode代码片段建议bug

java 反射代码片段

2020.7.24第十九天

7-异常处理