堆栈辗转开解(stack-unwinding):如果一个函数中出现异常,在当前函数内即通过 try..catch 捕捉(且捕捉到)的话,可以继续往下执行;如果不捕捉(或未捕捉到)就会抛出(与通过 throw 显式抛出效果相同)到外层函数,则当前函数会终止运行,释放当前函数内的局部对象(局部对象的析构函数就自然被调用了),外层函数如果也没有捕捉到的话,会再次抛出到更外层的函数,该外层函数也会退出,释放其局部对象……如此一直循环下去,直到找到匹配的 catch 子句,如果找到 main 函数中仍找不到,则调用名为 terminate 的标准库函数,该函数在 exception 头文件中定义,导致程序非正常退出。可以通过 std::set_terminate(fun) 来指定 terminate 调用的函数来输出一些信息。
异常是通过抛出对象而引发的,抛出的对象类型决定应该激活哪个处理代码,被选中的处理代码是距离抛出异常最近的匹配的 catch 子句。
#include <iostream> #include <string> void fun1() { throw "this is char* exception"; } void fun2() { throw std::string("this is string exception"); } int main() { try { fun1(); } catch(const char* e) { std::cout<<e<<std::endl; } try { fun2(); } catch(std::string& e) { std::cout<<e<<std::endl; } }
标准库异常类:
1、#include <exception>
定义了最常见的异常类,类名是 exception ,这个类只通知异常的产生,不提供更多信息。被 <stdexcept> 头文件包含
2、#include <stdexcept>
定义了几种常见的异常类,如: exception,runtime_error,range_error,out_of_range 等,都是 exception 的子孙类。
3、#include <new>
定义了 bad_alloc 异常类
4、#include <type_info>
定义了 bad_cast 异常类
标准异常类型只定义了 what() 函数,返回 const char* 字符串,用来提供详细的异常描述信息。what() 返回什么,是由标准异常类的构造函数决定的,像 exception, bad_alloc, bad_cast 类型只有默认构造函数,所以错误信息是不可定制的,而其它标准异常类如 runtime_error 只定义了一个带 string 参数的构造函数,这种异常类型的 what() 就可以在构造函数时定制了。
#include <iostream>#include <stdexcept> void fun() { throw std::runtime_error("this is my runtime_error"); } void fun2() { throw std::exception(); } int main() { try { fun(); fun2(); }catch(std::runtime_error& e) { std::cout<<e.what()<<std::endl; } catch(std::exception& e) { std::cout<<e.what()<<std::endl; } }
可以自定义异常类,继承 exception ,实现 what() 函数:
#include <iostream> #include <string> #include <exception> class MyException : public std::exception { std::string error_msg; public: explicit MyException(const std::string& _error_msg):error_msg(_error_msg) { } const char* what() { return error_msg.c_str(); } ~MyException() noexcept{} //旧式写法: ~MyException() throw(){} //throw(int) 只能抛出 int 型异常;throw() 不抛出任何异常 //throw() 已经被 c++11 标记为过时,被 noexcept 取代 }; void fun() { throw MyException("this is myexception"); } int main() { try { fun(); } catch(MyException& e) { std::cout<<e.what()<<std::endl; } }
不继承 exception 类,也可以使用,那继承它有什么好处呢?
1、统一的规范的接口,what() 函数。
2、可以在捕捉语句中使用 catch(std::exception& e) 来捕捉。
如果被抛出的异常对象是派生类类型的,但由基类类型的 catch 处理,那么,catch 不能使用派生类的特有的任何成员,也不会有多态行为产生。
另外,通过 catch(...) 可以捕捉所有的异常,包括自定义的异常(也包括不从 exception 继承的异常)。如果这样来看,其实不从 exception 继承也没什么,最后使用 catch(...) 把关。
析构函数应该禁止抛出异常,因为当抛出异常时,局部对象析构,而如果此时该局部对象析构函数里也抛出异常的话,则 C++ 无法同时处理两个异常,就会调用 terminate 函数终止程序。如果析构函数里存在异常,应该在析构函数内部处理,而不应当抛出。
构造函数中可以抛出异常,但发生异常时,该对象可能只是部分被构造,由于对象没有构造完成,不会执行析构函数,所以要自己保证能适当的撤销已构造的成员。C++拒绝为没有完成构造函数的对象调用析构函数,原因是避免开销,因为只有在每个对象里加一些字节来记录构造函数执行了多少步,它会使对象变大,且减慢析构函数的运行速度。所以只能自己手动进行资源释放,比较麻烦。一般建议不要在构造函数里做过多的资源分配,而应该把这些操作放在一个类似于 init 的成员函数中去完成。这样当 init 成员函数抛出异常时,如果对象是在栈上,析构函数仍会被调用(异常会自动销毁局部对象,调用局部对象的析构函数),如果是在堆上,需要在捕获异常之后 delete 对象来调用析构函数。
在 catch 子句中,可以通过 throw; 重新抛出异常。