AOP-Chap19-Error Handling and Exceptions

1 C-Style Error Handling

  • check返回值并报错
someStruct * readInput(const char * filename) {
	FILE * f = fopen(filename, "r"); 
	if (f == NULL) {
		return NULL; 
	}
	someStruct * ans = malloc(sizeof(*ans)); 
	if (ans == NULL) {
		fclose(f); //maybe check this too
		return NULL; 
	}
	while (...) { 
		...
		someType * ptr = malloc(sizeof(*ptr)); 
		if (ptr == NULL) {
			//code to free up all memory we have allocated so far 
			fclose(f); //maybe check this too
			return NULL;
		}
		... 
	}
	fclose(f);//maybe check this too 
	return ans;
}
void someOtherFunction() {
	...
	someStruct * input = readInput(inputFileName); 
	if (input == NULL) {
   		//do whatever we need to do here
	} 
	...
}		

2 C+±style: Exceptions

  • c++引入了exception,它涉及两个概念: throwtry/catch。程序员将代码放置在try块中可能发生错误的地方(她计划处理的地方)。try block后面紧跟着一个或多个catch块。每个catch块指定如何处理特定类型的异常。检测到无法处理的错误的代码会throw异常来指示问题
  • 一旦抛出异常,它就会沿call stack向上传播,迫使每个函数return(这样它们的每个stack frame都会被销毁),直到找到合适的异常handler,也就是直到它被try/catch block捕获。每次销毁stack frame时,都会像往常一样调用destructor来清理object;但是,如果这些析构函数中的一个抛出了从析构函数传播出去的异常,则程序崩溃。因此,通常应该设计析构函数来确保不会发生这种情况

3 Executing Code with Exceptions

throw std::exception();
  • 该表达式构造一个未命名的临时对象(通过其默认构造器,其类型为std::exception),然后抛出结果对象。然后异常向上传播到call stack,迫使函数返回并销毁它们的框架,直到它找到一个合适的exception handler,它的形式是一个try块,后跟一个可以捕获兼容类型异常的catch块
  • 可以抛出任何类型的object,但都是subtypes of std::exception
  • #include <exception>
  • #include <stdexcept>
  • 也可以自己写std::exception的subtypes,通过declare a class that publicly继承std::exception 或者是任何已经存在的subtypes
  • http://www.cplusplus.com/reference/exception/exception/
  • 一旦抛出异常,控制权就会转移到最近的合适的异常处理程序,可能会展开堆栈unwinding the stack(迫使函数返回,销毁它们的框架,并执行那些框架中的对象的析构函数)。异常处理程序是用try和catch编写的
try {
	//code that might throw
}
catch (std::exception & e) {
	//code to handle a generic exception
}	
  • if a statement of the form throw; (throw with no expression following it—just a semicolon after it ) is encountered inside of the catch block, then the exception being handled is re-thrown (the exception handling process starts again from step 2进行match—no extra copy is made).
  • Executing code with exceptions
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 对于catch block,可以指定它catch any type,但这样做不能bind the exception to a variable
try {
	//code that might throw
}
catch (...) {
	//code to handle any exception
}	
  • 对于try block,有function try block,用于在构造器中捕获initializer list的异常
class X {
	Y aY;
	Z aZ;
public:
	X() try : aY(42), aZ(0) { //格式
		//constructor’s body here as normal
	}
	catch(std::exception & e) {
   		//code to handle the exception	
   	}
   	//other members here as normal.
};   		
  • 如果异常发生在初始化列表或构造器中,它将在函数之后被handler捕获(假设它匹配)。然而,由于对象不能正确初始化,异常会自动重新抛出(就像throw;是函数try块的最后一行。任何成功构造的内容(包括对象的父类部分)都在进入handler之前被销毁

4 Exceptions as Part of a Function’s Interface

  • exception specification异常说明——可能抛出的异常类型的列表。用throw()并在括号中列出异常类型,添加到函数中
  • 括号中为空 --> 函数不能抛出任何类型的异常
  • 完全省略exception specification --> 函数可以抛出任何类型的异常
int f(int x) throw(std::bad_alloc, std::invalid_argument); //抛出2种可能的异常类型
int g(int * p, int n) throw(); //throw no exceptions
int h(int z); //throw any type of exception
  • 当重载一个提供异常说明的方法时,所重载的方法(在子类中)必须具有与继承方法的异常说明相同或更严格的异常说明
//Option 1: same as the parent class
int f(int x) throw(std::bad_alloc, std::invalid_argument);
//Option 2: more restrictive: cannot throw std::invalid_argument anymore 
int f(int x) throw(std::bad_alloc);
//Option 3: more restrictive: cannot throw std::bad_alloc anymore 
int f(int x) throw(std::invalid_argument);
//Option 4: more restrictive: cannot throw any exception 
int f(int x) throw();

5 Exception Corner Cases

  • unexpected() is called when a function throws an exception that is not allowed by its exception specification --> 默认行为depends on whether the exception specification allows std::bad_exception --> 如果允许,the unexpected() function throws a std::bad_exception and exception handling continues normally --> 如果不允许,then unexpected() calls terminate()
  • terminate() function的默认行为 is to terminate the program by calling abort()
  • terminate()发生在(1)an exception that propagates outside of main; (2)an exception that propagates out of a destructor during stack unwinding;(3) throw; when no exception is being handled);(4) as well as by the default implementation of unexpected()
  • 也可以自定义,通过calling set_unexpected or set_terminate, passing in a function pointer to specify the new behavior of calls to unexpected() or terminate() respectively
  • terminate handler --> the supplied function must terminate the program
  • unexpected handler --> the function may either throw an appropriate exception or terminate the program
  • 在对象构造期间抛出异常。此时已初始化的部分将被销毁,对象的memory被freed(即使是用new分配的)
  • 如果是array of objects(用new[]),如果第N个抛出异常,objects从N-1到0,按构造顺序的反方向被destroyed

6 Using Exceptions Properly

  • 异常是针对错误条件的 --> 不应该在代码段的正常执行路径上使用异常
  • 抛出本地创建的未命名临时对象 --> 抛出异常的唯一方法是throw exn_type(args);注意该语句中没有new
  • 仅使用throw;重新re-throw抛出异常 --> 不带参数
  • 通过(possibly constant)reference捕获异常 --> catch(exn_type_name & e)或catch(const exn_type_name & e)
  • 按最具体到最不具体的顺序declare handlers --> 如果为同一个try块声明多个catch块,应该按照从最特定类型到最不特定类型的顺序声明它们
    请添加图片描述
  • 析构函数永远不应该抛出异常 --> 如果它们执行任何可能抛出的操作,则必须找到适当处理它的方法(使用try/catch)
  • Exception types应该继承std:: exception --> 如果自己定义inherit(publicly) from std::exception or one of its subclasses
  • 保持异常类型简单 --> 如果编写自己的异常类,它不应该有任何可以抛出异常的行为。通常,应该使异常类完全没有动态分配(new可以抛出std::bad_alloc)。
  • 在自己的异常类型中重写what方法 --> std::exception类已经声明了这个方法
virtual const char * what() const throw();

此方法提供对发生的异常的描述。它返回一个C风格的字符串(const char *,而不是std::string)。通常,应该重写此方法以只返回a string literal

  • 注意所处理的所有代码的异常行为 --> 当调用另一个函数时,应该知道它可能抛出什么异常

7 Exception Safety

  • exception safety—what guarantees the code makes in exceptional circumstances
  • Strong Exception Guarantee
IntArray & operator=(const IntArray & rhs) {
	if (this != &rhs) {
		int * newData = new int[rhs.size]; 
		for (int i = 0; i < rhs.size; i++) {
  			newData[i] = rhs.data[i];
		}
		delete[] data; 
		data = newData; 
		size = rhs.size;
	}
	return *this;
}	
  • No Exception Guarantees
IntArray & operator=(const IntArray & rhs) {
	if (this != &rhs) {
		delete[] data;
		data = new int[rhs.size];
		for (int i = 0; i < rhs.size; i++) {
			data[i] = rhs.data[i];
		}
		size = rhs.size;
	}
	return *this;	
}	
/*
new操作符可能会抛出异常(如果不能满足内存分配请求)。如果抛出此异常,它将在operator=(该操作符不具备处理这种情况的能力)之外传播。但是,在抛出异常时,数据已经被删除,该对象的状态无效。即使称之为赋值运算符的代码可以处理一个内存分配失败的异常,它试图分配的对象在corrupted state(其数据指针悬空),使用时将导致程序崩溃或销毁(析构函数将double free数据)。
*/

在这里插入图片描述

  • Destructors should always provide a no-throw guarantee
  • perform the operation that might fail before we make any changes to the object
template<typename T>
class MyArray {
	T * data;
	size_t size;
public:
	//other methods elided
	MyArray<T> & operator=(const MyArray<T> & rhs) { 
	if (this != &rhs) {
		T * newData = new T[rhs.size];
		try {
			for (int i = 0; i < rhs.size; i++) {
	  			newData[i] = rhs.data[i];
			}
		}
		catch(std::exception & e) {	
			delete[] newData; 
			throw;
		}
		delete[] data; 
		data = newData; 
		size = rhs.size;
	}
	return *this;	
}

7.1 Resource Acquisition Is Initialization

  • an object in the local frame that is constructed when the resource is allocated and whose destructor frees that resource (unless we explicitly remove the resource from that object’s control). This design principle is called Resource Acquisition is Initialization (or RAII for short)
  • templated class --> std::auto_ptr, which is designed to help write exception safe code with the RAII paradigm
//an example that does not do anything particularly useful
X * someFunction(A & anA, B & aB) {
	std::auto_ptr<X> myX(new X());
	std::auto_ptr<Y> myY(new Y());
	aB.someMethod(myX.get()); //someMethod takes an X *
	*myY = anA.somethingElse(); //dereference pointer owned by myY 
	return myX.release(); //remove ownership of pointer, and return it
} //myY will delete the Y pointer it owns	
  • std::auto_ptr does not work with arrays (as it uses delete and not delete[]).
  • The Boost Library (see http://www.boost.org/) provides a boost::interprocess::unique_ptr<T,D> templated class. The second template argument specifies how to delete the owned pointer, making it possible to use it properly with arrays.

7.2 Exception Safety Idiom: Temp-and-Swap

  • 通过创建一个临时对象(相同类型)来修改对象,然后将新创建的临时对象的内容与原始对象交换 --> 强异常保证,因为所有修改都发生在新创建的临时对象上。如果出现任何错误,temp将在销毁堆栈帧期间被销毁,原始对象将保持不变。如果修改成功完成,那么修改后的对象的内容将与原始对象交换,这将更新原始对象的状态,并将其旧的状态留在临时对象中(该临时对象将在函数结束时销毁)
class SomeClass {
	void makeSomeModifications() { 
		SomeClass temp(*this);
		//make changes to temp
		//...
		std::swap(*this, temp); 
	}
};	
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值