生命周期

异常在被调函数中触发,不断传向下一级主调函数,直到被捕获

ps:若异常无法被捕获,程序会调用库函数terminate,由库函数调用abort函数终止程序

关键字

1. throw

throw + 表达式:抛出异常

ps:表达式可以是数据,也可以是函数(必须有返回值)

2. try + catch

pscatch (...)表示通用捕获,任何异常均可捕获,但是无法获取异常的值,一般放在所有catch最后

注:catch (基类异常)可以捕获派生类异常,若基类异常和派生类异常同存,一般将基类异常的捕获放在后面

1
2
3
4
5
6
7
8
9
10
11
12
try
{
...
}
catch (exception &e)
{
...
}
catch (...)
{
...
}

实例

除零异常

分析:try块处理了三个除法(15~17行);第二个除法除数为0,divide抛出被除数8被捕获并打印异常信息

pstry当中抛出异常后的语句无法执行,对应第三个除法(17行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
using namespace std;

float divide(float x, float y)
{
if (0 == y)
throw x;
return x / y;
}

int main()
{
try
{
cout << "5 / 2 = " << divide(5, 2) << endl;
cout << "8 / 0 = " << divide(8, 0) << endl;
cout << "7 / 1 = " << divide(7, 1) << endl;
}
catch(float e)
{
cout << e << " is divided by zero!" << endl;
}

return 0;
}

异常接口声明

1
void func() throw(A, B, C, D)	// A, B, C, D表示可能抛出的异常类型;func()可能抛出异常,须放在try当中
1
void func() throw()		// 表示func()函数一定不会抛出异常

异常处理中的构造与析构

基本原理

从对应的try块开始到异常被抛出之间构造(且尚未析构)的所有自动对象进行析构

ps:若异常不被捕获则无法析构

实例

分析:主要观察demo的析构是否被执行

异常可以被捕获:

demo在退出try块之后正常析构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
using namespace std;

class MyException
{
public:
MyException(const string &message) : message(message) {}
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 demo;
cout << "Throw MyException in func()" << endl;
throw MyException("exception thrown by func()");
}

int main()
{
try
{
func();
}
catch(MyException &e)
{
cout << e.getMessage() << endl;
}

return 0;
}
正常析构

异常无法被捕获:

库函数terminate调用abort函数终止程序,demo未被析构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
using namespace std;

class MyException
{
public:
MyException(const string &message) : message(message) {}
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 demo;
cout << "Throw MyException in func()" << endl;
throw MyException("exception thrown by func()");
}

int main()
{
try
{
func();
}
catch(int e)
{
cout << e << endl;
}

return 0;
}
无法析构

常见问题

  • 异常没有被捕获导致应该自动化析构的对象没有被释放

解决方案

  • 在所有catch最后加一个catch(...)捕获所有类型的异常

标准程序库的异常类

头文件

1
#include <stdexcept>

异常类汇总

点击查看

常用异常类

  • exception:标准程序库异常类的公共基类
  • logic_error:表示可以在程序中被预先检测到的异常(这类异常可以避免)
  • runtime_error:表示难以被预先检测到的异常

实例

三角形面积计算

判断边长是否合理(为正数且满足三角不等式),不合理抛出invalid_argument异常;合理则输出三角形面积

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
#include <cmath>
#include <stdexcept>
using namespace std;

double area(double a, double b, double c) throw(invalid_argument)
{
// 判断边长是否为正
if (a <= 0 || b <= 0 || c <= 0)
{
throw invalid_argument("The side length should be positive!");
}

// 判断边长是否满足三角不等式
if (a + b <= c || a + c <= b || b + c <= a)
{
throw invalid_argument("The side length should fit the triangle inequaltion!");
}

// 海伦公式计算面积
double aver = (a + b + c) / 2;
return sqrt(aver * (aver - a) * (aver - b) * (aver - c));
}

int main()
{
double a, b, c, res;
cout << "请输入三角形边长:";
cin >> a >> b >> c;

try
{
res = area(a, b, c);
}
catch (invalid_argument& e)
{
cout << e.what() << endl;
return 0;
}

cout << "三角形的面积为:" << res << endl;
return 0;
}

编写异常安全程序的原则

  • 明确哪些操作绝对不会抛掷异常:

    1. 基本数据类型的绝大部分操作
    2. 指针的赋值
    3. 算数运算和比较运算
    4. STL容器的swap函数
  • 尽量确保析构函数不抛掷异常,如果必须抛掷,须在抛掷异常前完成资源释放

避免异常引发的资源泄露

常见问题

  • 函数在抛出异常以前没有释放应该由它负责释放的资源(例如用new分配的变量是不可以自动析构的,必须使用free)

解决方案

  • 把一切动态分配的资源都包装成栈上的对象,利用抛掷异常时自动调用对象析构函数的特性来释放资源
  • 对于必须在堆上构造的对象,可以用智能指针auto_ptr加以包装