1. 异常对象保存
异常对象保存,指的是当异常发生时,我们把它捕获下来,但并不马上处理,而是先存储起来。这样一来,我们就可以在程序最后,或者其他并行任务的进程、线程里再去处理它。
接下来,我们要了解两个问题,一是如何保存异常对象,二是如何在其他的函数中去处理已经保存的异常对象。
exception_ptr save_ex; // 保存异常对象
class MyException
{
public:
MyException(string err)
{
m_err = err;
}
void show_err() const
{
cout << m_err << endl;
}
public:
string m_err;
};
void my_function1()
{
throw MyException("异常发生");
}
void handle_exception()
{
// 我们只是将异常对象进行了保存,此时如果需要重新处理,我们就需要重新将其抛出并处理
try
{
// 重新抛出保存的异常
rethrow_exception(save_ex);
}
catch (const MyException &ex)
{
ex.show_err();
}
}
void test01()
{
try
{
my_function1();
}
catch (const MyException& ex)
{
// 正常的情况下,捕获到异常之后需要马上进行处理
// ex.show_err();
// 将捕获到的异常对象保存起来,在今后某个时机再去处理
// 如何保存异常对象到 save_ex 中呢?
// save_ex = make_exception_ptr(ex);
save_ex = current_exception();
}
// test01 的最后处理异常
handle_exception();
}
2. 栈回退及清理
当异常发生的时候,throw 语句后面的代码就不再执行了,也就是说,正常的函数执行流程被打断了。那这里就有一个问题:如果在 throw 之前,我们创建了 N 个对象,这 N 个对象都调用了构造函数。由于后续代码不再执行,这些对象该怎么销毁呢?
当异常被抛出时,编译器会保证栈上创建的对象能够被正确地析构。
不过有些情况下,这种自动机制并不能保证所有对象都被及时释放,比如动态分配出来的对象。
class Student
{
public:
Student()
{
cout << "Student 构造函数" << endl;
}
~Student()
{
cout << "Student 析构函数" << endl;
}
};
void test02()
{
// 使用智能指针来管理动态对象,从而避免异常机制的栈回退过程中,内存泄漏问题的发生。
unique_ptr<Student> s(new Student);
throw 1;
cout << "hello world" << endl;
}
3. 异常处理流程
- 当调用 throw 抛出一个异常的时候,编译器会保存异常对象地址、析构函数地址、异常对象的类型信息。
- 执行栈回退、清理工作。
- 编译器判断发生异常的位置是不是在当前函数中,以及在当前函数中的哪个 try 块中,然后匹配 catch 块,并进入执行。
- 如果在当前函数中没有找到发生异常的 try,或者 catch 没有匹配上异常,就会将异常上抛给当前函数的调用者。
- 接下来,会再执行栈回退、清理,继续查找和匹配 try…catch 语句块。
- 最终都没有匹配到,编译器就会调用 std::terminate 函数,将当前程序终止。
在这个过程中,我们得到一条重要信息:在栈回退过程中,析构函数的调用不建议再抛出异常。原因有两点。
- 当在析构函数中再抛出异常,会导致 throw 后续的代码无法被执行。假设后续是一些非常重要的清理工作,就无法完成了。
- 栈回退的过程中,本来就在处理一个异常。此时在原来异常还没有处理完的情况下,再抛出一个新的异常,可能会导致程序直接终止。
可是析构函数中难免要写一些处理代码,我也不能保证析构函数一定不会发生异常。那这种时候该怎么办呢?
- 把析构函数中可能发生异常的代码放到 try 块中,自己消化吸收掉。
- 判断一下当前有没有异常产生,如果没有的话,再酌情抛出异常。
class Teacher
{
public:
Teacher()
{
cout << "Teacher 构造函数" << endl;
}
~Teacher()
{
cout << "Teacher 析构函数" << endl;
#if 0
try
{
throw - 2;
}
catch (...)
{
}
#endif
// uncaught_exception 判断是否有异常产生
// 如果为 true, 说明有异常产生
// 如果为 false , 说明没有异常产生
if (uncaught_exception())
{
cout << "有异常抛出" << endl;
}
}
};
void A()
{
Teacher t;
throw -1;
}
构造函数中允许抛出异常吗?
是允许的。构造函数负责对象的构建过程,由于它没有返回值,所以我们无法直接知晓对象构建过程中产生的问题。
这时候,就可以在构造函数中抛出一个异常,用来返回对象构建过程中的错误。
从上面学习到的异常机制中,我们能够明白:异常机制是一个较为复杂的容错机制。它能够大大提高代码的健壮性,但同时也会带来一些额外的开销。

冀公网安备13050302001966号