当前位置: 首页 > news >正文

[C++]异常

我们知道,C语言处理错误的方式:

  1. 返回错误码,缺陷:需要程序员自己查找错误信息。
  2. 终止程序(如assert),缺陷:用户难以接受。

一、 异常的概念

由于C语言中处理错误的方式不够理想和方便,C++推出了自己处理错误的方式——异常。当一个函数遇到无法处理的错误时,会抛出异常,让函数的调用者来处理。

二、 异常的定义

先看看以下代码:

void func()
{throw "error";
}void test()
{try{func();}catch (const char* str){cout << str << endl;}catch (...){cout << "未知异常" << endl;}
}

注意:

  1. try语句可以和多条catch语句连接,这样子可以捕获不同类型的异常
  2. 异常与对应类型的catch相匹配
  3. catch(...)可用于捕获任意类型的异常 ,防止未知的异常导致程序出错终止

三、异常的使用

1.异常的栈展开匹配

#include<iostream>
using namespace std;
double Division(int len, int times)
{if (times == 0){throw"除零错误";}else{return (double)len / times;}
}
void func()
{throw 1;
}
void Func()
{try{int len, times;cin >> len >> times;cout << Division(len, times) << endl;func();}catch (const char* str){cout << str << endl;}}
int main()
{try{Func();}catch (const char* str){cout << str << endl;}catch (...){cout << "未知异常" << endl;}return 0;
}

这里顺序是这样的,

  1. 先检查throw是否在try语句块中,如果在,则匹配相应的catch语句。
  2. 如果匹配不到或者不在try语句中,则推出当前函数栈,继续在调用函数的栈中进行查找匹配的catch语句。
  3. 如果到达main函数的栈,还是没有找到与之匹配的catch则会终止程序
  4. 如果匹配得到相应的catch语句并处理后,会继续执行catch后面的语句

值得注意的是:

在异常被抛出时,会创建一个异常对象的拷贝或移动,这个拷贝或移动的结果,是用于调用栈里向上传递的,直到找到于之匹配的catch语句块,这是为了保证抛出后,原始对象的状态的改变不会影响已经抛出的异常

在C++中抛出异常时,会创建一个异常对象的拷贝或者移动,以此来确保异常对象在抛出点和捕获点之间的生命周期和安全性。在C++11以后的版本中,如果异常类型支持移动语义并且操作是高效的,那么编译器偏向于使用移动构造来抛出异常。

2.异常的重新抛出

有时候我们可能发现在catch后不立即处理异常,而是选择重新抛出(rethrow)它,以便于让上层代码有机会处理它

在C++中,异常处理通常通过try-catch块实现。某些情况下,当前代码可能需要记录或部分处理异常,但仍希望上层调用者能够捕获并进一步处理。此时可以通过重新抛出(rethrow)实现。

try {// 可能抛出异常的代码
} catch (const std::exception& e) {// 记录异常信息std::cerr << "Log: " << e.what() << std::endl;// 重新抛出throw;
}

实际应用场景:

1.资源清理后重新抛出:

void processFile() {std::ifstream file("data.txt");try {if (!file) throw std::runtime_error("File open failed");// 处理文件内容} catch (...) {file.close(); // 确保资源释放throw; // 重新抛出异常}
}

在这个场景里可能因为抛异常导致不能让资源释放,所以现在catch语句里先释放空间,再将异常重新抛出。

异常重新抛出时,不会创建一个新的异常对象的拷贝或移动,而是继续传播当前捕获的异常对象

这个过程是高效的,因为它避免了不必要的对象的拷贝或移动,然而,这个也意味着在catch块中修改捕获的异常对象的状态可能会影响到上层代码对于这个对象的处理,因为它们是同一个对象,因此在重新抛出异常前,通常不建议修改捕获的异常对象的状态。

注意事项

  • 重新抛出的异常会保留原始异常类型和调用栈信息。
  • throw;只能在catch块中使用,否则会导致std::terminate
  • 若需要修改异常类型(如场景2),需显式抛出新异常而非使用throw;

2.异常安全

C++中异常会频繁导致资源泄漏的问题,比如在new和delete中间抛出来了异常,导致内存泄漏,在lock和unlock之间抛出异常导致死锁,C++通常使用RAII  来解决上面的问题(智能指针章节会讲到)

3.异常规范

//这个表示这个函数会抛出A/B/C/D中的某种类型
void func() throw(A,B,C,D);
//这个表示这个函数指挥抛出bad_allow的异常
void* operator new(std::size_t size) throw(std::bad_allow)
//这个表示不会抛出异常
void&operator delete (std::size_t size,void*ptr) throw();//C++11中新增的noexcept表示不后悔抛出异常
thread() noexcept;
thread(thread&& x) noexcept; 

四、自定义异常体系

实际上,异常的抛出和捕获,在某种情况下不需要类型一一对应,那就是抛出派生对象,用基类来捕获(在实际中非常好用的做法)

#include <iostream>
#include <exception>
#include <string>// 基类异常
class BaseException : public std::exception {
public:virtual const char* what() const noexcept override {return "BaseException occurred";}
};// 派生类异常1
class DerivedExceptionA : public BaseException {
public:const char* what() const noexcept override {return "DerivedExceptionA occurred";}
};// 派生类异常2
class DerivedExceptionB : public BaseException {
public:const char* what() const noexcept override {return "DerivedExceptionB occurred";}
};void riskyFunction(int type) {if (type == 1) {throw DerivedExceptionA();}else if (type == 2) {throw DerivedExceptionB();}
}int main() {try {riskyFunction(1);  // 测试派生类A// riskyFunction(2);  // 测试派生类B}catch (const BaseException& e) {  // 基类捕获所有派生类异常std::cerr << "Caught exception: " << e.what() << std::endl;}return 0;
}

用基类引用统一捕获不同的派生类对象,实现多态,规范了异常体系,防止乱抛异常导致项目程序终止。

五、标准库异常

C++标准库定义了一套异常类体系,均派生自std::exception基类,用于处理程序运行时的错误。这些异常类覆盖了常见错误场景,如内存分配失败、逻辑错误、范围越界等。

  1. std::exception
    所有标准库异常的基类,提供虚成员函数what()返回错误描述。

    try { /* 可能抛出异常的代码 */ } 
    catch (const std::exception& e) {std::cerr << "Error: " << e.what() << std::endl;
    }
  2. std::runtime_error
    表示程序运行时才能检测的错误(如文件打开失败)。
    派生类包括:

  3. std::logic_error
    表示程序逻辑错误(如无效参数)。
    派生类包括:

    ps:operator[]不会做边界检查,如果你尝试访问一个越界的索引,operator[]会返回对应位置的引用,但是这是一个未定义的行为,可能会导致程序崩溃或其他不可预测的结果。

  4. std::bad_alloc
    内存分配失败时由new抛出。

    try { int* arr = new int[1000000000000]; } 
    catch (const std::bad_alloc& e) {std::cerr << "Memory allocation failed: " << e.what() << std::endl;
    }
  5. std::bad_cast
    dynamic_cast对引用类型转换失败时抛出。

PS:C++标准异常库设计的不够好用,实际上很多公司都是自定义一套异常体系


自定义异常类

可通过继承std::exception或其派生类实现自定义异常:

class MyException : public std::runtime_error {
public:MyException(const std::string& msg) : std::runtime_error(msg) {}
};try { throw MyException("Custom error"); } 
catch (const MyException& e) {std::cerr << e.what() << std::endl;
}


异常安全实践

  • RAII原则:通过智能指针(如std::unique_ptr)管理资源,避免内存泄漏。
  • noexcept规范:对不抛出异常的函数标记noexcept以优化性能。
  • 避免异常滥用:仅对异常情况使用异常(如关键错误),而非控制流程。

异常与性能

异常处理可能引入额外开销,但在现代编译器中,若无异常抛出,性能影响可忽略。建议在错误处理复杂或跨多层函数调用时使用异常。


文章转载自:

http://oWt8Lyvh.qjtbt.cn
http://GpzfU3nw.qjtbt.cn
http://uvLbpkVP.qjtbt.cn
http://QsZ1Achu.qjtbt.cn
http://KjMRoSqf.qjtbt.cn
http://u3sVnz0R.qjtbt.cn
http://hMgFczU8.qjtbt.cn
http://yaiCEHGV.qjtbt.cn
http://PMvvaDe5.qjtbt.cn
http://n54xdRqZ.qjtbt.cn
http://mOMTcBWu.qjtbt.cn
http://IDjpDdy7.qjtbt.cn
http://WQHDMchm.qjtbt.cn
http://HDashr9I.qjtbt.cn
http://eA8hO9Gp.qjtbt.cn
http://u5Hnfa4p.qjtbt.cn
http://F1WyWEfI.qjtbt.cn
http://GlVa9aeD.qjtbt.cn
http://qpyukTyq.qjtbt.cn
http://dI0DFzco.qjtbt.cn
http://vI34Qv3w.qjtbt.cn
http://OEuOS590.qjtbt.cn
http://O39EaJ5s.qjtbt.cn
http://b6ytghfz.qjtbt.cn
http://w2v0uwVE.qjtbt.cn
http://Pvzo7Jlq.qjtbt.cn
http://2Pm6qE1p.qjtbt.cn
http://B60mvgEn.qjtbt.cn
http://rUVZ6FDK.qjtbt.cn
http://AVVf8s1W.qjtbt.cn
http://www.dtcms.com/a/388286.html

相关文章:

  • Windows PE 文件结构详解:从入口到执行的旅程
  • LLM 处理 PDF 表格的最佳方法:从解析到高效利用
  • 自动驾驶中的传感器技术50——Radar(11)
  • WALL-OSS--自变量机器人--2025.9.8--开源
  • GJOI 9.11/9.13 题解
  • 基于Spark的用户实时分析
  • 什么是 Conda 环境?
  • RK3506开发板QT Creator开发手册,交叉编译工具链与QT应用示例,入门必备
  • 颠覆3D生成,李飞飞团队新研究实现3D场景「无限探索」,AI构建世界模型能力跨越式进化
  • 3D 大模型生成虚拟世界
  • AI技术全景图:从大模型到3D生成,探索人工智能的无限可能
  • 一天认识一种模型方法--3D人体建模 SMPL
  • World Labs 的核心技术介绍:生成持久、可导航的 3D 世界
  • websocket如何推送最新日志
  • 使用Docker部署bewCloud轻量级Web云存储服务
  • web Service介绍
  • Web 架构中的共享存储:NFS 部署与用户压缩
  • RuoYi整合ZLM4j+WVP
  • @CrossOrigin的作用
  • Tree-shaking【前端优化】
  • Scikit-learn Python机器学习 - 分类算法 - 随机森林
  • 深入浅出Java中的Happens-Before原则!
  • centos7更换yum源
  • [特殊字符] 认识用户手册用户手册(也称用户指南、产品手册)是通过对产品功能的清
  • Codex 在 VS Code/Cursor 的插件基础配置
  • 前端Web案例-登录退出
  • Redis学习------------缓存优化
  • openfeigin 跨服务调用流程 源码阅读
  • 运动手环心率监测:原理、可靠性与市场顶尖之选全解析​​
  • 端到端智驾测试技术论文阅读