异常(C++)

news/2024/11/20 1:48:04/

异常

  • 前言
  • 一、程序的错误分类
  • 二、异常
    • 1. 概念
    • 2. 捕获异常的关键字和格式
    • 3. 异常的使用
      • 异常的原则
      • 异常再抛出
      • 异常说明
      • 注意事项
    • 4. 自定义异常体系
    • 5. C++标准库的异常体系
  • 三、总结

前言

  1. 在程序运行时经常碰到一些错误,例如年龄、身高不能为负,除数为0等,这些错误放到程序中如果不加以管制,程序就会崩溃。C++提供了异常机制,让我们能跟捕获运行时错误,给程序一次机会,给用户一个反馈。
  1. C++异常处理机制可以让我们捕获并进行处理错误,然后我们可以通过捕获后的判断,重新给程序指一条明路,或者在程序结束之前,做一些必要的工作,例如将错误写到日志等。

说到C++,C语言的处理错误方式也简单提一下:
assert宏

  1. 生产环境可能被禁用(会带来性能开销,并且不能对用户提供任何实际好处)编译器会优化掉。
  2. 开发环境可以用来检查条件,但是不提供详细的错误信息,并且终止程序。
  3. 只能用来检查布尔表达式,无法用于更复杂的逻辑和错误处理。

返回错误码

  1. 程序员要自己查找对应的错误,系统很多库函数也都是通过错误码(errno)表示错误

一、程序的错误分类

程序的错误大致可以分为三种,分别时语法错误、逻辑错误和运行时错误。

  1. 语法错误:在编译和链接阶段就能发现,必须全符合语法规则才能生成可执行代码。这种错误最简单。
  2. 逻辑错误:编码思路问题,执行的结果不是预定的,也可以通过调试解决。
  3. 运行时错误:程序运行期间发生的错误,如同除0,越界等。C++异常(Exception)机制就是为了解决这种错误。

二、异常

1. 概念

异常:异常是一种处理错误的方式,当一个函数发现自己无法处理的错误时就可以抛异常,让函数的直接或间接调用者处理这个错误。

2. 捕获异常的关键字和格式

C++异常处理涉及三个关键字

  • throw:出现问题,程序使用throw抛出异常
  • catch:有抛出也得有捕获,catch关键字就是用来捕获异常。捕获的地方就是处理问题的地方。
  • try:try块中的代码为保护代码,如果出错会激活特定的异常,后面跟着一个或多个catch块。

eg:语法

try{//保护区,就是可能会出错的代码
}
catch(ExceptionName e1){//出错后,通过异常处理程序捕获异常,在这里处理问题或者 再抛出
}
catch(ExceptionName e2){//因为异常对象类型的多样,所以可以多个catch块捕获匹配的异常
}
catch(...){//如果try块中出现异常,而前面的catch块也没有捕获,这是最后一道防线,所有类型都会在这里捕获,但不知道异常的错误原因。
}

注意: 可能有些不好理解,后面内容我会逐步介绍

3. 异常的使用

异常的原则

简单的例子:

double Division(int a, int b)
{if (b == 0)throw "Division by zero condition!";elsereturn (double)a / (double)b;
}void Func()
{int len, time;cin >> len >> time;cout << Division(len, time) << endl;
}int main()
{try {Func();}catch (const char* errmsg){cout << errmsg << endl;}catch (...){cout << "unknow exception" << endl;}return 0;
}

异常 栈调用
调用链

上例的函数调用链中栈的细节:

  1. 检查被throw是否在try块中,如果在并触发异常。
  2. 被throw抛出对象的类型和最近的catch块的参数进行匹配,匹配成功,则调到catch块中处理。没有匹配成功,则退出当前函数栈,在调用该函数的栈中继续匹配。
  3. 如果到main函数的栈依旧没有匹配,则终止程序(非正常结束)。但一般有catch(…)兜底。
  4. 如果匹配到catch子句处理完块中内容后,会沿着try,catch语句后继续执行

匹配原则

  1. throw抛出的对象类型与调用链中位置最近并且参数类型一样的catch匹配。
  2. throw抛出异常会生成一个异常对象的拷贝(因为抛出的对象可能是一个临时对象),这个拷贝的对象会被catch以后销毁。(类似函数传值返回)
  3. catch(…)可以捕获任意类型异常,但是不知道异常错误是什么。
  4. 匹配原则的例外:可以抛出派生类对象,使用基类捕获。(后面讲)

异常再抛出

一个单独的catch语句不能完整的处理某个异常,再执行一些校正操作之后,可能会抛给调用链更上一层的函数接着处理异常。
这个简单的校正工作可能就是改变参数内容,但是这里又要注意是不是引用
eg:

//简单的校正操作
catch (my_error& eObj){                     //引用类型 eObj.status = errCodes::severeErr;      //修改了异常对象throw;
}
catch (other_error eObj) {                     //非引用类型 eObj.status = errCodes::severeErr;         //只修改了异常对象的局部副本throw;
}

异常再抛出:

try
{//保护代码//...
}
catch (const char* errmsg)
{cout << errmsg << endl;
}
catch (...)
{throw;//异常再抛出,不含任何表达式//注意异常进行抛出后,是直接跳过这个函数栈的后面部分,例如下面的打印语句就  不会在进行执行cout << "unknow Exception" << endl;
}

异常说明

目的是为了让使用者知道该函数抛出的异常类型。

异常说明:

//这里表示这个函数会抛出A/B/C/D中的某种类型的异常
void func()  throw(A, B, C, D);//这里表示这个函数只会抛出bad_alloc的异常
void* operator new (std::size_t size) throw (std::bad_alloc);//这里表示这个函数不会抛出异常
void* operator delete (std::size_t size, void* ptr) throw ();//若无异常接口声明:表示这个函数可以抛出任何类型的异常
void func1()

C++11新增的noexcept,表示不会抛出异常

void recoup(int) noexcept; //不会抛出异常
void alloc(int)            //可能抛出异常

注意: noexcept的位置也很特殊,因为const,final,override或者虚函数=0,都可以跟在函数后面,所以noexcept要在const及引用限定符之后,在final,override或者虚函数=0之前。

注意事项

建议

  1. 不要在构造函数中抛出异常(可能导致对象未完成初始化)
  2. 不要在析构函数中抛出异常(可能导致资源泄漏)
  3. 不要在lock和unlock之间抛异常(死锁)(后面的博客讲)

4. 自定义异常体系

实际很多公司会自己定义异常体系,规范管理。大家抛出的都是继承的派生类对象,捕获一个基类即可。

继承体系

eg:服务器开发中使用的异常继承体系(例子)

class Exception
{
public:Exception(const string& errmsg, int id):_errmsg(errmsg),_id(id){}virtual string what() const{return _errmsg;}protected:string _errmsg;int _id;
};class SqlException : public Exception
{
public:SqlException(const string& errmsg, int id, const string& sql):Exception(errmsg, id), _sql(sql){}virtual string what() const{string str = "SqlException:";str += _errmsg;str += "->";str += _sql;return str;}private:const string _sql;
};class CacheException : public Exception
{
public:CacheException(const string& errmsg, int id):Exception(errmsg, id){}virtual string what() const{string str = "CacheException:";str += _errmsg;return str;}
};class HttpServerException : public Exception
{
public:SqlException(const string& errmsg, int id, const string& type):Exception(errmsg, id), _type(type){}virtual string what() const{string str = "HttpServerException:";str += _type;str += ":";str += _errmsg;return str;}private:const string _type;
};

调用捕获:

void SQLMgr()
{srand(time(0));if (rand() % 7 == 0){throw SqlException("权限不足", 100, "select * from name = '张三'");}//throw "xxxxxx";
}void CacheMgr()
{srand(time(0));if (rand() % 5 == 0){throw CacheException("权限不足", 100);}else if (rand() % 6 == 0){throw CacheException("数据不存在", 101);}SQLMgr();
}void HttpServer()
{// ...srand(time(0));if (rand() % 3 == 0){throw HttpServerException("请求资源不存在", 100, "get");}else if (rand() % 4 == 0){throw HttpServerException("权限不足", 101, "post");}CacheMgr();
}int main()
{while (true){Sleep(500);      //引用系统头文件<Windows.h>try {HttpServer();}catch (const Exception& e) // 这里捕获父类对象就可以{// 多态cout << e.what() << endl;}catch (...){cout << "Unkown Exception" << endl;}}return 0;
}

5. C++标准库的异常体系

C++标准库的异常体系
注意: C++标准库的异常体系设计的不好用,所以一般都是定义自己的异常体系

三、总结

异常总体而言利大于弊

异常的优缺点

优点:

  1. 异常对象定义好了,可以更加清晰准确的展示出错误的各种信息,更好的定位bug。
  2. 不需要层层返回检查并错误码,异常体系直接跳到catch捕获的位置
  3. 很多第三方库都包含异常,想要使用这些库也需要使用异常
  4. 部分函数使用异常更好处理,例如没有返回值的或者不便于返回的。

缺点:

  1. 执行流乱跳,导致跟踪调试时以及分析程序困难,很混乱
  2. 性能的开销
  3. C++标准库的异常体系定义的不好,大家各自定义异常体系,不统一

http://www.ppmy.cn/news/1260371.html

相关文章

如何在Spring Boot中优雅地重试调用第三方API?

文章目录 1. 引言2. 重试机制的必要性3. Spring Retry简介4. Spring Boot中使用Spring Retry实现重试4.1 添加依赖4.2 配置重试策略4.2.1 代码示例 4.3 降级处理4.3.1 代码示例 5. 异步重试5.1 异步方法的重试5.1.1 代码示例 5.2 异步方法的降级处理5.2.1 代码示例 6. 异常分类…

linux向日葵开机自启动

有个服务需要先开启: sudo systemctl start runsunloginclient.service 开机自启动服务: sudo systemctl enable runsunloginclient.service 然后再启动就可以了 sudo systemctl status runsunloginclient.service 开机自启后进行检查service服务状态 开发板ubuntu系统上如…

网络安全(一)--网络环境构成,系统的安全

2. 网络攻防环境 目标 了解攻防环境构成了解入侵检测系统&#xff08;平台&#xff09;的部署位置 2.1. 环境构成 2.1.1. 环境框图 一个基本的网络攻防实验环境包括&#xff1a;靶机、攻击机、入侵检测分析系统、网络连接四部分组成。 一个基础的网络攻防实验环境需要如下…

组件的生命周期

目录​ 1&#xff1a;生命周期和生命周期函数的概念 2&#xff1a;组件创建的过程 3&#xff1a;组件创建阶段beforeCreate&#xff0c;created&#xff0c;beforeMount&#xff0c;mounted生命周期函数。 3.1&#xff1a;beforeCreate方法示例&#xff1a; 3.2&#xff1…

封装校验规则(以及复选框和整体校验)-----Vue3+ts项目

登录校验页面 <script setup lang"ts"> import { ref } from vue import { mobileRules, passwordRules } from /utils/rules const mobile ref() const password ref() </script><!-- 表单 --><van-form autocomplete"off">&l…

使用GPT-4V解决Pycharm设置问题

pycharm如何实现关联&#xff0c;用中文回答 在PyCharm中关联PDF文件类型&#xff0c;您可以按照以下步骤操作&#xff1a; 1. 打开PyCharm设置&#xff1a;点击菜单栏中的“File”&#xff08;文件&#xff09;&#xff0c;然后选择“Settings”&#xff08;设置&#xff09;。…

【EI会议征稿】第五届大数据与信息化教育国际学术会议(ICBDIE 2024)

【往届检索】第五届大数据与信息化教育国际学术会议&#xff08;ICBDIE 2024&#xff09; 2023 5th International Conference on Big Data and Informatization Education 第五届大数据与信息化教育国际学术会议&#xff08;ICBDIE 2024&#xff09;定于2024年01月19-21日在…

Java网络编程——非阻塞通信

对于用ServerSocket以及Socket编写的服务器程序和客户程序&#xff0c;它们在运行过程中常常会阻塞。例如当一个线程执行ServerSocket的accept()方法时&#xff0c;假如没有客户连接&#xff0c;该线程就会一直等到有了客户连接才从accept()方法返回。再例如当线程执行Socket的…