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; }
构造函数中允许抛出异常吗?
允许的。构造函数是对象构建过程,由于没有返回值,所以无法知晓对象构建过程中的产生的问题。
此时,可以在构造函数中抛出一个异常,用于返回对象构建过程中的错误。
从学习到的异常机制中,能够明白:异常机制是一个较为复杂的容错机制。这个机制能够很大提高代码的健壮性。但是,也需要一些很大额外的开销。