C++异常的使用

1.传统错误

C语言中,传统的错误处理方式有:

  1. assert(),缺陷就是太过于武断,会直接终止程序,并且只能在debug模式下才可以起作用
  2. 返回错误码,在Linux编程中就十分常见,但是对于很深层的系统调用,传递给最外层的main()函数时需要很多的返回语句,并且不是所有函数都可以返回错误码的(例如:类的几大成员函数,构造函数、析构函数等)

因此实际上C都是通过错误码来处理错误的,部分情况下使用终止程序处理严重错误。

2.异常概念

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

  • throw:当问题出现时,通过使用throw关键字,程序就会抛出某个类型的异常
  • catch:该关键字可以捕获异常,可以设置多个类型的捕获
  • trytry块中的代码标识将被激活的某些异常,它后面通常跟着一个或多个catch块,异常通常在这里被抛出,捕获异常则会使用catch

3.异常使用

throw /*抛出异常*/
//...
try {//正常执行
}
catch (/*接收异常*/){//代码 1
}
catch (/*接收异常*/){//代码 2
}
catch (/*接收异常*/){//代码 2
}

假设我们写一个除法,有可能出现除零错误

#include <iostream>
using namespace std;class Data
{
public:Data(int data = 100): _data(data){cout << "构造" << '\n';}~Data(){cout << "析构 " << '\n';}
private:int _data;
};double Division(int left, int right)
{if (right == 0){throw "除零错误";}return (double)left / (double)right;
}void Func()
{Data d1;//虽然抛出异常,但是这里的析构也会调用int left, right;cin >> left >> right;cout << Division(left, right) << '\n';Data d2;//如果发生除零错误,这里以后代码就不会被执行,因此 d2 不会调用构造和析构
}int main()
{//一般都在外层代码捕获try{Func();}catch (const char* str){cout << str << '\n';}catch (...){cout << "Other" << '\n';}return 0;
}

在函数调用链中异常栈的展开匹配原则:

  1. 首先检查throw本身是否在try块内部
  2. 如果是再查找匹配的catch语句
  3. 如果有匹配的接受异常的类型,则调到catch的地方进行处理(这一行为很像函数的传参,抛出的异常对象可能是一个临时对象,所以会生成一个拷贝对象,这个拷贝的临时对象会在catch后销毁)
  4. 没有匹配的catch则退出当前函数栈,继续在调用函数的栈中进行查找匹配的catch
  5. 如果到达main函数的栈,依旧没有匹配的,则终止程序(行为类似assert

上述这个沿着调用链查找匹配的catch子句的过程称为“栈展开”。所以实际中我们最后都要加一个catch(...)捕获任意类型的异常,否则当有遗漏的异常没被捕获,程序就会像assert()一样直接终止程序。

而找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行。

另外,关于异常还有一些其他的语法和关键字。

//这里表示这个函数会抛出 A、B、C、D 中的某种类型的异常,但只是作为提示,不会限制用户只能抛出自己提示的类型(形同虚设)
void fun() 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();//C++ 11 中新增的 noexcept,表示不会抛异常,但是这个关键字更好,就算内部真的抛出了错误也不会捕获该异常(可惜有些编译器只能在运行后才可以报错,不是写代码时)
thread() noexcept;
thread (thread&& x) noexcept;

还有些时候单个catch不能完全处理一个异常,在进行一些校正后,希望再交给更外层的调用链来处理,catch则可以通过语句throw;重新抛出,则将异常传递给更上层的函数处理。

#include <iostream>
using namespace std;double Division(int a, int b)
{if (b == 0){throw "Division by zero condition";}return (double)a / (double)b;
}void Function()
{int* array_1 = new int[10];//(6)new 本身也会抛出异常int* array_2 = new int[10];//(7)new 本身也会抛出异常try {int len, time;cin >> len >> time;cout << Division(len, time) << '\n';//(1)假设抛出“除零异常”}catch (...)//(2)“除零异常”被代码拦截下来{//(3)先释放 array,防止内存泄露cout << "delete_1[] " << array_1 << '\n';delete[] array_1;cout << "delete_2[] " << array_2 << '\n';delete[] array_2;throw;//(4)重新将异常抛出去}cout << "delete_1[] " << array_1 << '\n';delete[] array_1;cout << "delete_2[] " << array_2 << '\n';delete[] array_2;
}int main()
{try{Function();}catch (const char* error)//(5)“除零错误”会在这里被接收{cout << error << endl;}catch (const exception& e)//(8)new 的异常可以在这里被接收{cout << e.what() << '\n';}return 0;
}

补充:这里假设有多个array资源,释放起来代码就会很长,我在下一篇文章《C++智能指针》会使用智能指针优化这一过程。

4.异常安全

异常的使用会让C++产生一些新的错误导致程序出现严重问题,因此我们需要遵循一些代码要求,避免危险的情乱:

  1. 构造函数完成对象的构造和初始化,最好不要在构造函数内抛出异常,否则可能导致对象构造不完整或没有完全初始化
  2. 析构函数完成对象的资源清理,最好不要在析构函数内抛出异常,否则可能导致资源泄漏(内存泄漏、句柄未关闭等等)

补充1C++中的异常经常会导致资源泄漏的问题,比如在newdelete中抛出了异常,导致内存泄漏,在lockunlock之间抛出了异常导致死锁,C++经常使用RAII来解决以上问题,关于RAII我们智能指针这节进行讲解。

补充2:异常没被捕获时,编译器一般都会调用abort()强行终止程序,像VS 2022就会弹出一个带有提示为abort() has been called的弹窗。

5.异常体系

但是异常如果没有一定的规范,就会导致程序迅速终止,这是比较严重的结果,因此就需要一种异常体系的规范,最典型的规范就是使用继承来防范异常的危险。

假设技术团队设计了一个父类,所有人只能抛出继承自父类的子类,这样只需要捕获父类对象即可(有切片和多态的存在就会很方便)。

这里模拟了一个后端服务器运行过程中子类抛异常,父类捕获异常的过程:

#include <iostream>
#include <thread>
using namespace std;//服务器开发中通常使用的异常继承体系
class Exception
{
public:Exception(const string& errmsg, int id):_errmsg(errmsg),_id(id){}//查询错误virtual string what() const{return _errmsg;}
protected:string _errmsg;	//错误信息int _id;		//错误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:HttpServerException(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 (1){this_thread::sleep_for(chrono::seconds(1));try {HttpServer();}catch (const Exception& e) //这里捕获父类对象就可以{//多态cout << e.what() << endl;}catch (...){cout << "Unkown Exception" << endl;}}return 0;
}

可以看到,如果设计了有关异常的父子类的继承体系,在捕获异常的时候还可以结合多态做出更多的行为。

补充:实际上C++自己也提供了类似的异常库,但是很多公司基本使用都是自己设定的异常库,因此这里就不细讲,您简单看下一些异常库文档即可,大致逻辑也是类似的。

6.异常意义

6.1.异常优点:

  1. 相比错误码的方式可以清晰准确的展示出错误的各种信息,甚至可以包含堆栈调用的信息,这样可以帮助更好的定位程序的bug
  2. 返回错误码的传统方式在最大的问题是深度很深,在函数调用链中,深层的函数返回了错误,那么就需要层层返回错误,直到最外层拿到错误
  3. 很多的第三方库都包含异常(比如:boostgtestgmock等等常用的库,使用它们也需要使用异常)
  4. 部分函数没有返回值,使用异常更好处理(比如:构造函数没有返回值,不方便使用错误码方式处理。T& operator这样的函数,若pos越界了只能使用异常或者终止程序处理,没办法通过返回值表示错误)
  5. OO类语言基本都会使用异常来处理程序运行过程中出现的错误,学习C++的异常后,理解其他语言的异常会容易一些(但机制不完全相同,切记不可照搬,辩证看待)

6.2.异常缺点

  1. 异常会导致程序的执行流乱跳,导致代码逻辑非常混乱(就类似goto语句的使用)
  2. 异常只在运行时抛出,这会导致难以跟踪调试代码、分析程序
  3. 异常会有一些性能的开销,但在现代硬件优化的情况下,基本可以忽略不计
  4. C++不像Java,没有垃圾回收机制,资源需要用户自己管理,有了异常反倒容易导致内存泄漏、死锁等异常等安全问(这个就需要使用RAII还有智能指针等来处理资源的管理问题,但这些的学习成本较高)
  5. C++标准库的异常体系定义得不太好,导致各家公司自定义了多种异常体系,非常的混乱,这点不如其他的语言
  6. 如果用户随意抛出异常,外层捕获的设计就会难以捕捉。因此最好保证抛出的异常类型都继承自一个父类,而函数是否抛异常、抛什么样的异常,都使用func() throw();这样的方式规范化、明确化(但是这点又不是强制性的,有点形同虚设的感觉)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/198958.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

14、SQL注入——HTTP文件头注入

文章目录 一、HTTP Header概述1.1 HTTP工作原理1.2 HTTP报文类型1.3 较重要的HTTP Header内容 二、HTTP Header注入2.1 HTTP Header注入的前提条件2.2 常见的HTTP Header注入类型 一、HTTP Header概述 1.1 HTTP工作原理 1.2 HTTP报文类型 &#xff08;1&#xff09;请求报文 …

国产光耦ORPC-817替代HCPL-817-36BE,在找国产的朋友联系沟通

国产光耦ORPC-817替代HCPL-817-36BE&#xff0c;在找国产的朋友联系沟通 北京冠宇铭通 肖小姐 1. 特性 (1)电流传递比(中频 5mA, VCE 5V时&#xff0c;CTR:最小50%) (2)输入输出隔离电压高(Viso 5000 vrms) (3)响应时间(tr: TYP。4s (VCE 2V, IC 2mA, RL 100Ω) (4) ESD…

软件设计模式原则(五)接口隔离原则

顾名思义&#xff0c;该原则说的是&#xff1a;客户端不应该依赖它不需要的接口。一个类对另一个类的依赖应该建立在最小的接口上。 一.定义 核心思想&#xff1a; 使用多个专门的接口比使用单一的总接口要好。一个类对另外一个类的依赖性应当是建立在最小的接口上的。一个接口…

博客文章SEO:提升博客排名和吸引更多读者的方法来啦!

互联网发展到现在&#xff0c;搜索引擎优化&#xff08;SEO&#xff09;一直发挥着不可替代的作用。搜索引擎的流量往往更加定向&#xff0c;来自搜索引擎的流量转化率更高&#xff0c;可以帮助企业更好地实现销售和推广目标。因此&#xff0c;通过合理的SEO策略&#xff0c;你…

Arduino驱动STS35数字温度传感器(温湿度传感器)

目录 1、传感器特性 2、硬件原理图 3、控制器和传感器连线图 4、驱动程序 STS35瑞士Sensirion公司新推出的温度传感器,STS35提供了一个完全校准、线性和供电电压补偿的数字输出&

服务器是否稳定怎么看

服务器是否稳定怎么看 在对服务器进行测试时&#xff0c;难免会遇到一些出错的现象&#xff0c;而这些现象都可以被很快的解决。那么服务器状态出错情况有哪些&#xff1f;怎么看服务器是否稳定&#xff1f;壹基比小鑫给大家说说这些出错状态的意思&#xff01; 一、怎么看服…

市场调研:中国南美白对虾养殖面积达到17.75万公顷

南美白对虾(学名:Litopenaeus Vannamei )是对虾科、滨对虾属动物。成体最长达23厘米&#xff0c;甲壳较薄&#xff0c;正常体色为青蓝色或浅青灰色&#xff0c;全身不具斑纹。步足常呈白垩状&#xff0c;故有白肢虾之称。南美白对虾额角尖端的长度不超出第1触角柄的2节&#xf…

从浅入深掌握进阶结构体(C语言)

前言 这一期我们将继续讲解结构体的知识&#xff0c;还没有看过上一期的小伙伴一定要赶紧去学习哦。 上一期&#xff0c;冲鸭&#xff01; 那么话不多说我们开始今天的学习吧&#xff01; 文章目录 1,结构体的自引用2,匿名结构体3,位段4,结构体的传参5,尾声 1,结构体的自引用 …

基因组注释流程

一边学习&#xff0c;一边总结&#xff0c;一边分享&#xff01; 详细教程请访问&#xff1a; 组学分析流程 本期分析流程 Hisat2-SamtoolsTrinity_GG_denovoPASA … 本期教程文章 题目&#xff1a;Genomic insights into local adaptation and future climate-induced vu…

【开源视频联动物联网平台】libmodbus 写一个Modbus TCP 客户端

libmodbus 是一个用于 Modbus 通信协议的 C 语言库&#xff0c;可以用来创建 Modbus TCP 客户端。以下是一个简单的示例代码&#xff0c;演示如何使用 libmodbus 创建一个 Modbus TCP 客户端。 首先&#xff0c;确保你已经安装了 libmodbus 库。你可以从 libmodbus 的官方网站…

FastAPI与BaseModel

from typing import Optionalfrom fastapi import FastAPI from pydantic import BaseModel #当一个模型属性具有默认值时&#xff0c;它不是必需的。否则它是一个必需属性。将默认值设为 None 可使其成为可选属性 app FastAPI() class Item(BaseModel):name:str #没有初始值都…

linux 安装 Apache 服务 并部署网站

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 公众号&#xff1a;网络豆云计算学堂 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a; 网络豆的主页​​​​​ 写在前面 哈喽大家我是网络豆&#xff0c;本章将会…

数据结构—二叉树

文章目录 10.二叉树(1).二叉树的基本概念(2).遍历#1.前序遍历#2.中序遍历#3.后序遍历#4.非递归中序遍历 (3).中序前/后序建树#1.中序前序遍历建树#2.中序后序遍历建树 (4).递归和二叉树基本操作#1.求树高#2.求结点数#3.求叶子结点数#4.复制树#5.判断两棵树是否相等 (5).特殊二叉…

零信任组件和实施

零信任是一种安全标准&#xff0c;其功能遵循“从不信任&#xff0c;始终验证”的原则&#xff0c;并确保没有用户或设备受信任&#xff0c;无论他们是在组织网络内部还是外部。简而言之&#xff0c;零信任模型消除了信任组织安全边界内任何内容的概念&#xff0c;而是倡导严格…

操作系统复习总结——文件管理

&#x1f525;博客主页&#xff1a;真的睡不醒 &#x1f680;系列专栏&#xff1a;深度学习环境搭建、环境配置问题解决、自然语言处理、语音信号处理、项目开发 &#x1f498;每日语录&#xff1a;但愿每次回忆&#xff0c;对生活都不感到负疚。 &#x1f389;感谢大家点赞…

搜维尔科技:AI时代,迈向2030元宇宙数字人戏曲教育数字化思维、战略与未来!

一场关于中国传统戏曲与数字媒体交汇的探讨之旅将于今日在清华大学开讲&#xff0c;本次活动旨在推动AI时代大背景下&#xff0c;利用元宇宙、数字人等创新技术焕发中国传统戏曲全新活力。 讲座以“AI时代&#xff0c;迈向2030元宇宙数字人戏曲教育数字化思维、战略与未来”为主…

【Linux系统编程】开发工具yum和vim

目录 一&#xff0c;yum工具的使用 1&#xff0c;yum的介绍 2&#xff0c;yum的使用 二&#xff0c;vim工具的开发 1&#xff0c;vim的介绍 2&#xff0c;模式的使用 3&#xff0c;vim配置文件 4&#xff0c;sudo配置文件 一&#xff0c;yum工具的使用 1&#xff0c;y…

弱口令防护和网站防盗链有什么用

弱口令防护主要针对用户账户的安全。弱口令是指容易被猜测或破解的密码&#xff0c;如常见的密码、简单的数字序列或字典中的单词等。弱口令防护的目的是防止恶意用户或攻击者通过猜测或暴力破解密码的方式获取合法用户的账户权限。通过实施强密码策略、密码复杂度要求和账户锁…

使用JavaScript实现页面注册表单文本框验证方法

文章目录 文章目录 概要 整体架构流程 技术名词解释 代码展示&#xff1a; 小结 需要素材即使联系我我会陆续更新的&#xff01;&#xff01; 概要 提示&#xff1a;这里可以添加技术概要 发送验证码模块 ②&#xff1a; 各个表单验证模块 ③&#xff1a; 勾选已经阅读同意模…

【EI会议征稿中|航空航天领域】第二届航空航天与控制工程国际学术会议(ICoACE 2023)

第二届航空航天与控制工程国际学术会议&#xff08;ICoACE 2023&#xff09; 2023 2nd International Conference on Aerospace and Control Engineering 2023年第二届航空航天与控制工程国际学术会议&#xff08;ICoACE 2023&#xff09;将于2023年12月15-17日在江苏南京召开…