C++异常
C++ 异常是程序运行时的意外事件,如除零、内存分配失败等。其机制可分离正常逻辑与错误处理,通过 throw 抛出异常对象,由匹配的 catch 捕获。抛出异常会生成对象拷贝,程序跳转至匹配 catch,沿途函数可能提前退出,局部对象会析构(栈展开),但堆上手动分配对象需智能指针避免泄漏。异常捕获需类型匹配,有派生类转基类等例外。main 函数宜用 catch (...) 作最后防线,可捕获所有异
C++异常
1. 什么是异常?
异常是程序在运行时可能发生的、超出正常执行流程的事件(或错误)。例如:除以零、内存分配失败、文件不存在、无效的用户输入等。
C++ 提供了一套结构化的机制来抛出(throw)和捕获(catch)这些异常,从而将正常的业务逻辑与错误处理代码清晰地分离开。
2. 异常的抛出与捕获
-
程序出现问题时,我们通过抛出(throw)⼀个对象来引发⼀个异常,该对象的类型以及当前的调⽤链决定了应该由哪个catch的处理代码来处理该异常。
-
catch通常捕获的是离抛出异常位置最近且与抛出对象类型匹配的,根据抛出对象的类型和内容,程序的抛出异常部分告知异常处理部分到底发⽣了什么错误。
-
当throw执⾏时,throw后⾯的语句将不再被执⾏。程序的执⾏从throw位置跳到与之匹配的catch模块,catch可能是同⼀函数中的⼀个局部的catch,也可能是调⽤链中另⼀个函数中的catch,控制权从throw位置转移到了catch位置。这⾥还有两个重要的含义:1、沿着调⽤链的函数可能提早退出。2、⼀旦程序开始执⾏异常处理程序,沿着调⽤链创建的对象都将销毁。
-
抛出异常对象后,会⽣成⼀个异常对象的拷⻉,因为抛出的异常对象可能是⼀个局部对象,所以会⽣成⼀个拷⻉对象,这个拷⻉的对象会在catch⼦句后销毁。
3. 栈展开
-
栈展开是当异常被抛出后,为了找到匹配的
catch块,C++ 运行时系统自动沿着函数调用链(即调用栈)向上回溯的过程。在这个过程中,它会析构所有已创建但尚未析构的局部对象。 -
这个过程确保了即使在异常发生时,资源也能被正确释放,避免了内存泄漏。
-
对于使用
new在堆上手动分配的对象,如果只有原始指针指向它,那么在栈展开过程中,这个对象不会被自动析构,会导致内存泄漏。- 解决方案:使用智能指针。
-
如果到达main函数,依旧没有找到匹配的catch⼦句,程序会调⽤标准库的 terminate 函数终⽌程序。
-
如果找到匹配的catch⼦句处理后,catch⼦句代码会继续执⾏。
4. 异常的一些注意事项
-
⼀般情况下抛出对象和catch是类型完全匹配的,如果有多个类型匹配的,就选择离他位置更近的那个。
-
类型完全匹配,但是也有一些例外情况。
-
权限缩小的转换
-
数组到指针的转换
-
函数到函数指针的转换
-
派生类到基类的转换(重要)
-
-
异常处理的最后防线
-
在C++异常处理中,如果异常一直传递到
main()函数仍然没有被捕获,程序就会异常终止。除非发生无法恢复的严重错误,否则我们通常不希望程序这样突然结束。因此,最佳实践是在main()函数的最后使用catch(...)作为安全网。 -
int main() { try { // 程序主要逻辑 runProgram(); } catch (const std::exception& e) { // 捕获标准异常 std::cerr << "标准异常: " << e.what() << std::endl; } catch (...) { // 捕获所有其他未知异常 std::cerr << "发生未知异常!" << std::endl; } return 0; } -
catch(…)的局限性
-
能捕获任意类型的异常
-
无法获取异常的具体信息
-
不能知道异常的类型或内容
-
-
-
异常的重新抛出。
-
异常重新抛出是指在
catch块中捕获异常后,再次抛出同一个异常,让外层的调用者继续处理。 -
重新抛出使用
throw;。
-
-
noexcept是 C++11 引入的关键字,用于指定函数不会抛出异常。 -
// 该函数承诺不会抛出任何异常 void myFunction() noexcept { // 函数实现 // 如果这里抛出了异常,程序会调用 std::terminate() } -
移动操作通常应该标记为 noexcept,移动操作标记为
noexcept能够让标准库容器和算法安全地使用移动语义而不是拷贝语义,从而获得显著的性能提升。
更多推荐


所有评论(0)