# 异常

  • 异常是程序运行过程中出现问题
  • "异常" 并不经常出现
  • 处理异常使程序像没有发生过问题一样继续执行

# 何时使用异常处理

  • 异常处理 用于处理同步错误 (语句执行时发生的错误)
  • 常见的异常处理错误:

数组下标越界,算法溢出,被零除,函数参数不合法,内存不够引起的内存分配不成功

  • 异常处理不是用于处理异步事件相关的错误的

磁盘读写结束,网络信息到达,点击鼠标或键盘,这些与程序的控制流程并行,独立

# 异常处理的基本思想

异常处理额基本思想

# 异常处理的语法

  • 抛掷异常的程序段
throw 表达式; // 表达式子可以是变量也可以是语句或小段代码
  • 捕获并处理异常的程序段
try
	复合语句(保护段)
catch (异常声明)
	复合语句(异常处理程序)
catch (异常声明)
	复合语句
	...
  • 若有异常则通过 throw 创建一个异常对象并抛掷
  • 将可能抛出异常的程序段嵌在 try 块之中。通过正常的顺序执行到达 try 语句,然后执行 try 块内的保护段
  • 如果在保护段执行期间没有引起异常,那么跟在 try 块后的 catch 子句就不执行。程序从 try 块后的最后一个 catch 子句后面的语句继续执行
  • catch 子句按其在 try 块后出现的顺序被检查。匹配的 catch 子句将被捕获并处理异常 (或继续抛掷异常)
  • 如果匹配的处理器未找到,则库函数 terminate 将被自动调用,其默认是调用 abort终止程序
/* 
处理除零异常
*/
#include <iostream>
using namespace std;
int divide(int x, int y)
{
	if (y == 0)
		throw x; // 通过 throw 创建 x 的异常对象抛掷给 x
	return x / y;
}
int main(void)
{
	try
	{
		cout << "5 / 2 = " << divide(5, 2) << endl;
		cout << "8 / 0 = " << divide(8, 0) << endl;
		cout << "7 / 1 = " << divide(7, 1) << endl;
	}
	catch (int e)
	{
		cout << e << " is divided by zero !" << endl;
	}
	cout << "That is ok." << endl;
	return 0;
}
/*
5 / 2 = 2
8 is divided by zero !
That is ok.
*/

/*
带析构语句的类的 C++ 异常处理
*/
#include <iostream>
#include <string>
using namespace std;
class MyException
{
public:
	MyException(const string& message) :message(message)
	{
	}
	~ MyException() {}
	const string& getMessage() const
	{
		return message;
	}
private:
	string message;
};
class Demo
{
public:
	Demo()
	{
		cout << "Constructor of Demo" << endl;
	}
	~Demo()
	{
		cout << "Destructor of Demo" << endl;
	}
};
void func() throw(MyException)
{
	Demo d;
	cout << "Throw MyException in func() " << endl;
	throw MyException("exception thrown by func()");
}
int main(void)
{
	cout << "In main function" << endl;
	try
	{
		func();
	}
	catch (MyException& e)
	{
		cout << "Caught an exception: " << e.getMessage() << endl;
	}
	cout << "Resume the exception of main()" << endl;
	return 0;
}
/*
In main function
Constructor of Demo
Throw MyException in func()
Destructor of Demo
Caught an exception: exception thrown by func()
Resume the exception of main()
*/

# 异常接口声明

异常声明 (异常规范, Exception specifications ) 一个函数显式声明可能抛出的异常,有利于函数的调用者为异常处理做好准备

  • 可以在函数的声明中列出这个哈数可能抛掷的所有异常类型
void fun() throw(A,B,C,D);
  • 若无异常接口声明,则此函数可以抛掷 任何类型 的异常
  • 不抛掷任何异常的函数声明如下
void fun() throw();

# noexcept 异常说明

  • 对明确 不会 抛出异常的函数使用 noexcept 说明修饰
  • 声明方式:
返回值类型 func(形参列表) noexcept;
  • 异常处理使编译和运行时有 额外开销 ,省去异常处理可优化 加速调用
  • 需保持该函数内部调用函数和定义语句均不会抛出异常的一致性
  • noexcept 运算符,可判断函数是否使用了 noexcept 说明
void f() noexcept;
noexcept(f());  // 返回 true,因为 f 有 noexcept 说明

# 慎用异常声明的情况

  • 对于带类型参数的函数模版,要尽量避免使用 exception specifications, 因为不同类型对于相同行为的定义不同,抛出的异常也就不同,因而函数模版很难或不可能确定它具现代的函数实体所可能抛出的异常
  • 使用回调 (callback) 函数时
  • 系统可能抛出的异常

# 异常处理中的构造与析构

# 自动的析构
  • 找到一个匹配的 catch 异常处理后
    • 初始化异常参数
    • 将从对应的 try 块开始到异常被抛掷之间构造 (且尚未析构) 的所有自动对象进行析构
    • 从最后一个 catch 处理之后开始恢复执行

# 标准程序库继承关系

标准程序库继承关系

# C++ 标准库各种异常类所代表的异常

异常类头文件异常的含义
bad_allocexception用 new 动态分配空间失败
bad_castnew执行 dynamic_cast 失败(dynamic_cast 参见 8.7.2 节)
bad_typeidtypeinfo对某个空指针 p 执行 typeid (*p)(typeid 参见 8.7.2 节)
bad_exceptiontypeinfo当某个函数 fun () 因在执行过程中抛出了异常声明所不允许的异常而调用 unexpected () 函数时,若 unexpected () 函数又一次抛出了 fun () 的异常声明所不允许的异常,且 fun () 的异常声明列表中有 bad_exception,则会有一个 bad_exception 异常在 fun () 的调用点被抛出
ios_base::failureios用来表示 C++ 的输入输出流执行过程中发生的错误
underflow_errorstdexcept算术运算时向下溢出
overflow_errorstdexcept算术运算时向上溢出
range_errorstdexcept内部计算时发生作用域的错误
out_of_rangestdexcept表示一个参数值不在允许的范围之内
length_errorstdexcept尝试创建一个长度超过最大允许值的对象
invalid_argumentstdexcept表示向函数传入无效参数
domain_errorstdexcept执行一段程序所需要的先决条件不满足

# 标准异常类的基础

  • exception :标准程序库异常类的 公共基类
  • logic_error :表示可以在程序中被预先检测到的异常
    • 如果小心地编写程序,这类异常能够避免
  • runtime_error : 表示 难以 被预先检测的异常
/*
计算三角形的面积
*/
#include <iostream>
#include <cmath>
#include <stdexcept>
using namespace std;
// 给出三角形三边长,计算三角形面积
double area(double a, double b, double c)
{
	// 判断三角形表长是否为正
	if (a <= 0 || b <= 0 || c <= 0)
	{
		throw invalid_argument("the side length should be positive");
	}
	// 判断三边长是否满足三角不等式
	if (a + b <= c || b + c <= a || c + a <= b)
	{
		throw invalid_argument("the side length should fit the triangle inequation");
	}
	// 由 heron 公式计算三角形面积
	double s = (a + b + c) / 2;
	return sqrt(s * (s - a) * (s - b) * (s - c));
}
int main(void)
{
	double a, b, c; // 三角形三边长
	cout << "Please input the side lengths of a triangle:";
	cin >> a >> b >> c;
	try
	{
		double s = area(a, b, c);// 尝试计算三角形面积
		cout << "Area:" << s << endl;
	}
	catch (exception& e)
	{
		cout << "Error:" << e.what() << endl;
	}
	return 0;
}