Coding In C++, Day07

 异常
一、为什么要有异常——WHY?
1.通过返回值表达错误
局部对象都能正确的析构
层层判断返回值,流程繁琐
2.采用<setjmp.h>里面定义的setjmp/longjmp远程跳转
一步到位进入错误处理,流程简单
setjmp是给C程序员使用的,根本没有考虑到C++程序员定义的类类型,直接采用setjmp实现跳转会使得某些对象(大部分的局部对象)失去被析构的机会,即使是栈对象。
3.异常处理
局部对象都能正确的析构
一步到位进入错误处理,流程简单
二、异常的语法——WHAT?
1.异常的抛出
throw 异常对象;
异常对象可以是基本类型的变量,也可以是类类型的对象。
当程序执行错误分支时抛出异常。
2.异常的捕获
try {
  可能抛出异常的语句块;
}
catch (异常类型1 异常对象1) {
  处理异常类型1的语句块;
}
catch (异常类型2 异常对象2) {
  处理异常类型2的语句块;
}
...
catch (...) {
  处理其它类型异常的语句块;
}
异常处理的流程:throw 异常对象 之后,立即开始寻找并执行try的右大括号,如果当前函数没有try右大括号则执行函数右大括号。始终沿着函数调用的逆序,依次执行右花括号,直到try的右花括号,保证所有的局部对象都能被正确地析构,然会根据异常对象的类型,匹配相应的catch分支,进行有针对性的错误处理。
三、异常处理的使用方法——HOW?
1.抛出基本类型的异常,用不同的值代表不同的错误。
2.抛出类类型的异常,用不同的类型表示不同的错误。
3.多采用跑出类类型的异常,通过类类型的异常可以携带更多诊断信息。
4.忽略异常和继续抛出异常。
5.异常说明
在一个函数的形参表后面写如下语句:
...形参表) throw (异常类型1, 异常类型2, ...) { ... } 表示这个函数可以被捕获的异常。
...形参表) throw () - 这个函数所抛出的任何异常均无法捕获。
...形参表) 没有异常说明 - 这个函数所抛出的任何异常均可捕获。
 (p.s. 派生类的虚函数不能抛出比基类被覆盖版本更多的异常类型,举例:
class A {
  virtual void foo (void)
    throw (int, double) { ... }
  virtual void bar (void)
    throw () { ... }
};
class B : public A {
  void foo (void)
    throw (int, char) { ... }
  // ERROR
  void bar (void) { ... } // ERROR
  void bar (void)
    throw () { ... }
};

)
6.使用标准异常:标准异常库定义了多种异常类

#include <stdexcept>

/*
 * 异常练习
 */
#include <iostream>
#include <cstdio>
using namespace std;
class A {
public:
	A (void) {
		cout << "A构造" << endl;
	}
	~A (void) {
		cout << "A析构" << endl;
	}
};
void func3 (void) {
	A a;
	FILE* fp = fopen ("none", "r");
	if (! fp) {
		cout << "throw前" << endl;
		throw -1;
		cout << "throw后" << endl;
	}
	cout << "文件打开成功!" << endl;
	// ...
	fclose (fp);
}
void func2 (void) {
	A a;
	cout << "func3()前" << endl;
	func3 ();
	cout << "func3()后" << endl;
	// ...
}
void func1 (void) {
	A a;
	cout << "func2()前" << endl;
	func2 ();
	cout << "func2()后" << endl;
	// ...
}
int main (void) {
	try {
		cout << "func1()前" << endl;
		func1 ();
		cout << "func1()后" << endl;
	}
	catch (int ex) {
		if (ex == -1) {
			cout << "执行失败!改天再见!" << endl;
			return -1;
		}
	}
	// ...
	cout << "执行成功!恭喜恭喜!" << endl;
	return 0;
}


/*
 *异常练习
 */
#include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std;
void foo (void) {
	FILE* fp = fopen ("none", "r");
	if (! fp)
		throw "打开文件失败!";
	void* pv = malloc (0xFFFFFFFF);
	if (! pv)
		throw "内存分配失败!";
	// ...
}
int main (void) {
	try {
		foo ();
	}
	catch (const char* ex) {
		cout << ex << endl;
		return -1;
	}
	return 0;
}
/*
 * 异常练习
 * */
#include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std;
class Error {};
class FileError : public Error {};
class MemError : public Error {};
void foo (void) {
	FILE* fp = fopen ("none", "r");
	if (! fp)
		throw FileError ();
	void* pv = malloc (0xFFFFFFFF);
	if (! pv)
		throw MemError ();
	// ...
}
int main (void) {
	try {
		foo ();
	}
	catch (FileError& ex) {
		cout << "打开文件失败!" << endl;
		return -1;
	}
	catch (MemError& ex) {
		cout << "内存分配失败!" << endl;
		return -1;
	}
	catch (Error& ex) {
		cout << "一般性错误!" << endl;
		return -1;
	}
	return 0;
}

/*
 * 异常处理
 * */
#include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std;
class Error {
public:
	virtual void print (void) const = 0;
};
class FileError : public Error {
public:
	FileError (const string& file, int line) :
		m_file (file), m_line (line) {}
	void print (void) const {
		cout << "在" << m_file << "文件的第"
			<< m_line << "行,发生了文件错误!"
			<< endl;
	}
private:
	string m_file;
	int m_line;
};
class MemError : public Error {
public:
	void print (void) const {
		cout << "内存不够啦!!!" << endl;
	}
};
void foo (void) {
	FILE* fp = fopen ("none", "r");
	if (! fp)
		throw FileError (__FILE__, __LINE__);
	void* pv = malloc (0xFFFFFFFF);
	if (! pv)
		throw MemError ();
	// ...
}
int main (void) {
	try {
		foo ();
	}
	catch (Error& ex) {
		ex.print ();
		return -1;
	}
	return 0;
}

/*
 *异常练习
 */
#include <iostream>
using namespace std;
void foo (void) {
	throw 10;
}
void bar (void) {
	try {
		foo ();
	}
	catch (int& ex) {
		--ex;
		throw; // 继续抛出
	}
	// ...
}
int main (void) {
	try {
		bar ();
	}
	catch (int& ex) {
		cout << ex << endl; // 9
	}
	return 0;
}


/*
 *异常练习
 */
#include <iostream>
using namespace std;
void foo (void) throw (int, double, const char*) {
//	throw 1;
//	throw 3.14;
	throw "Hello, Exception !";
}
int main (void) {
	try {
		foo ();
	}
	catch (int ex) {
		cout << ex << endl;
	}
	catch (double ex) {
		cout << ex << endl;
	}
	catch (const char* ex) {
		cout << ex << endl;
	}
	return 0;
}

/*
 * 异常处理练习
 */
#include <iostream>
#include <stdexcept>
#include <cstdio>
using namespace std;
class FileError : public exception {
private:
	const char* what (void) const throw () {
		return "文件访问失败!";
	}
};
class B {
public:
	B (void) {
		cout << "B构造" << endl;
	}
	~B (void) {
		cout << "B析构" << endl;
	}
};
class C {
public:
	C (void) {
		cout << "C构造" << endl;
	}
	~C (void) {
		cout << "C析构" << endl;
	}
};
class A : public C {
public:
	A (void) : m_b (new B) {
		FILE* fp = fopen ("none", "r");
		if (! fp) {
			delete m_b;
			throw FileError ();
		}
		// ...
		fclose (fp);
	}
	~A (void) {
		delete m_b;
	}
private:
	B* m_b;
//	C m_c;
};
int main (void) {
	try {
		A a;
		// ...
	}
	catch (exception& ex) {
		cout << ex.what () << endl;
		return -1;
	}
	return 0;
}



四、构造函数中的异常
构造函数可以抛出异常,而且有些时候还必须抛出异常,以通知调用者构造过程中所发生的错误。
构造函数抛出异常后,开始执行try的右大括号,如果没有则执行函数右大括号。
如果在一个对象的构造过程中抛出了异常,那么这个对象就称为不完整对象。不完整对象的析构函数永远不会被执行,但构造函数在未能成功构造完整对象的情况下会自动回滚(逆序析构已经创建的成员), 但因为回滚不会释放已经动态申请的资源,所以还是需要在throw之前,手动释放动态的资源。
五、析构函数中的异常
析构函数也可以抛出异常,但是会出现操作系统吐核,出现未定义错误。举例:
class A {
    public:
    ~A(void) {
        throw -1;
    }
};

main()
{
    try {
    A a;//构造a
    } //遇到块作用域结束大括号(try右大括号)开始析构a, 析构a时候抛出异常对象,抛出异常对象后最终执行try右大括号,遇到try右大括号开始析构a...这样就死循环了,部分编译器(例如gcc)经过编译期间处理就避免了死循环,但是部分编译器就没有处理这个问题,最终程序执行可能导致未定义错误,操作系统吐核。
    catch(int & a) {
    }
}

永远不要在析构函数中抛出异常。可以通过try{}catch(...){}来全面拦截所有可能引发的异常,让析构函数没有可能向外抛出异常。例如:
class A {
public:
  ~A (void) {
    //throw -1;
    try {
      sysfunc ();
    }
    catch (...) {}
  }
};
try {
  A a;
  a.foo ();
}
catch (...) { ... }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值