【C++】C++异常

文章目录

  • 1. C语言传统处理错误的方式
  • 2. C++异常的概念
  • 3. 异常的使用
    • 3.1 异常的抛出和捕获
    • 3.2 异常的重新抛出
    • 3.3 异常安全
    • 3.4 异常规范
  • 4. C++标准库的异常体系
  • 5. 自定义的异常体系
  • 6. 异常的优缺点

1. C语言传统处理错误的方式

C语言传统的错误处理机制有两个:

  1. 终止程序,比如assert。

    缺陷:用户难以接收。如发生内存错误,除0错误时就会终止程序。

  2. 返回错误码

    缺陷:需要程序员自己去查找对应的错误。如系统的很多库的接口函数都是通过把错误码放到errno中,表示错误

实际中C语言基本都是使用返回错误码的方式处理错误,部分情况下使用终止程序处理非常严重的错误

2. C++异常的概念

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

  • throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
  • catch: 在您想要处理问题的地方,通过异常处理程序捕获异常.catch 关键字用于捕获异常,可以有多个catch进行捕获。
  • try: try 块中的代码标识将被激活的特定异常,它后面通常跟着一个或多个 catch 块。

如果有一个块抛出一个异常,捕获异常的方法会使用 try 和 catch 关键字。try 块中放置可能抛出异常的代码,try 块中的代码被称为保护代码。使用try/catch语句的语法如下所示:

try
{//保护的标识代码
}
catch(ExceptionName e1)
{//catch块
}
catch(ExceptionName e2)
{//catch块
}
catch(ExceptionName eN)
{//catch块
}

3. 异常的使用

3.1 异常的抛出和捕获

异常的抛出和匹配原则

  • 异常是通过抛出对象引发的,该对象的类型决定了应该激活哪个catch的处理代码
  • 选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个
  • 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch以后销毁。(这里的处理类似于函数的传值返回)
  • catch(…)可以捕获任意类型的异常,问题是不知道异常错误是什么
  • 在实际抛出和捕获异常的原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象,使用基类捕获,这个在实际中非常实用

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

  • 首先检查throw本身是否在try块内部,如果是再查找匹配的catch语句。如果有匹配的,则调用到catch的地方进行处理
  • 没有匹配到catch则退出当前函数栈,继续再上层调用函数的栈中进行查找匹配catch
  • 如果到达main函数的栈,依旧没有匹配的,则终止程序。上述这个沿着调用链查找匹配的catch子句的过程称为栈展开。所以实际中我们最后都要加一个catch(…)捕获任意类型的异常,否则当有异常没捕获,程序就会直接终止
  • 找到匹配的catch子句并处理之后,会继续沿着catch子句后面继续执行

image-20230811010149061

double Division(int a, int b)
{// 当b == 0时抛出异常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;
}
void Test1()
{try {Func();}catch (const char* errmsg){cout << errmsg << endl;}catch (...) {cout << "unkown exception" << endl;}
}

image-20230811010453845

3.2 异常的重新抛出

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

我们看下面一段代码:

double Division(int a, int b)
{// 当b == 0时抛出异常if (b == 0)throw "Division by zero condition!";elsereturn ((double)a / (double)b);
}
void Func()
{// 这里可以看到如果发生除0错误抛出异常,另外下面的array没有得到释放。// 所以这里需要捕获所有类型的异常,然后先将array释放,然后再将捕获的异常重新抛出,交给外面处理int* arr = new int[10];try{int len, time;cin >> len >> time;cout << Division(len, time) << endl;}catch (...){cout << "delete []" << arr << endl;delete[] arr;throw;}//...cout << "delete []" << arr << endl;delete[] arr;
}
void Test2()
{try{Func();}catch (const char* errmsg){cout << errmsg << endl;}catch (...){cout << "unkown exception" << endl;}
}

3.3 异常安全

C++的异常还存在一些安全问题

  • 构造函数完成对象的构造和初始化,最好不要在构造函数中抛出异常,否则可能导致对象构造或者初始化不完全
  • 析构函数主要完成资源的清理操作,最好不要在析构函数中抛出异常,否则可能会导致资源泄漏(内存泄漏或者句柄未关闭等)。
  • C++中异常经常会导致资源泄漏的问题,比如在new和delete中抛出了异常,导致内存泄 漏,在lock和unlock之间抛出了异常导致死锁,C++经常使用RAII来解决以上问题

3.4 异常规范

由于不规范使用异常会带来许多非常严重的后果,所以 C++98 引入了异常规范,异常规范中建议程序员对每个函数进行异常接口说明,其目的是让函数使用者知道该函数可能抛出的异常有哪些,如下:

  1. 在函数后面接throw(类型)列出这个函数可能抛掷的所有异常类型
  2. 函数的后面接throw(),表示函数不抛异常
  3. 若无异常接口声明,则此函数可以抛掷任何类型的异常
//这里表示func可以抛出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();

但是由于 C++98 函数异常接口只是建议性做法,而不是语法硬性要求的,同时还由于写出一个函数可能抛出的所有异常比较麻烦,所以 C++98 的异常规范在实际开发中几乎没有人遵守,形同虚设;

为了让人们能够对函数进行异常接口说明,C++11 对异常接口说明进行了简化

  • 在函数后面加上关键字noexcept表示该函数不会抛出任何异常
  • 函数后面不加关键字则表示可能抛出任意类型的异常
// C++11 中新增的 noexcept,表示不会抛异常
thread() noexcept;
thread(thread&& x) noexcept;

并且 C++11 还对使用 noexcept 修饰的函数进行了检查,如果该函数被 noexcept 修饰,但是可能会抛出异常,则编译器会报一个警告,但并不影响程序的正确性:

int at(vector<int>& v, size_t pos) noexcept
{if (pos >= v.size())throw out_of_range("数组越界");elsereturn v[pos];
}void Test1()
{vector<int> v = {1, 2, 3, 4, 5};try {at(v, 5);}catch (out_of_range& errmsg){cout << errmsg.what() << endl;}catch (...) {cout << "未知异常" << endl;}
}

请添加图片描述

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

C++提供了一系列标准的异常,定义在exception中,我们可以在程序中使用这些标准的异常。它们是以父子类层次结构组织起来的,如下所示:

img

请添加图片描述

其中比较常见的错误有:bad_alloc:new空间失败时抛出此异常;runtime_error:运行时错误,比如除0错误等;out_of_range数组越界错误。

C++标准库的异常体系的使用:

void Test2()
{vector<int> v(10);try {v.reserve(100000000);v.at(20) = 5;}catch (exception& errmsg){cout << errmsg.what() << endl;}catch (...){cout << "unknow Exception" << endl;}
}

请添加图片描述

虽然我们可以直接使用 C++ 标准提供的这些异常,也可以去继承 exception 类来实现自己的异常类,但在实际开发中很多企业都会自己定义一套单独的异常继承体系,因为C++标准库设计的不够好用。再加上我们平时自己写代码基本不会使用异常,所以对于 C++ 标准异常我们作为了解内容即可。

5. 自定义的异常体系

实际上,很多公司都会对自己定义的异常体系进行规范化管理,因为如果在一个项目中,大家随意的抛出异常,那么最外层的调用者就没法玩了,所以在实际中都会定义一套继承的规范体系:定义一个最基础的基类,所有人抛出的异常对象都是继承与该异常类的派生类对象,所以异常语法可以用基类捕获抛出的派生类对象,所以最外成值需要捕获基类就行了

img

下面我们看一下服务器开发中通常使用的异常继承体系

class Exception//这里定义了一个异常的基类,后面对于不同类型的异常,将进行不同的继承
{
public:Exception(const string& errmsg, int id):_errmsg(errmsg),_id(id){}virtual string what() const//这里的what使用virtual修饰,以达到多态使用,返回不同的错误信息{return _errmsg;}
protected:string _errmsg;int _id;
};
class SqlException : public Exception//sql类型的异常
{
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;//保存出现错误的sql语句
};
class CacheException : public Exception//Cache类型异常
{
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(nullptr));if (rand() % 4 == 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();//如果没有抛出异常就调用SQL层操作
}
void HttpServer()
{// ...srand(time(0));if (rand() % 3 == 0){throw HttpServerException("请求资源不存在", 100, "get");}else if (rand() % 4 == 0){throw HttpServerException("权限不足", 101, "post");}CacheMgr();//如果网络服务没有问题,在这里调用内存操作
}
void Test3()
{while(1){this_thread::sleep_for(chrono::seconds(1));try{HttpServer();//这里调用网络服务}catch (const Exception& e) // 这里捕获父类对象就可以{// 多态cout << e.what() << endl;}catch (...){cout << "Unkown Exception" << endl;}}
}

请添加图片描述

6. 异常的优缺点

C++异常的优点

  1. 异常对象定义好了,相比错误码的方式可以清晰准确的展示出错误的各种信息,甚至可以包含堆栈调用的信息,这样可以帮助更好的定位程序的bug
  2. 返回错误码的传统方式有个很大的问题就是,在函数调用链中,深层的函数返回了错误,那么我们得层层返回错误,最外层才能拿到错误;
  3. 很多的第三方库都包含异常,比如boost、gtest、gmock等等常用的库,那么我们使用它们也需要使用异常;
  4. 部分函数使用异常更好处理,比如构造函数没有返回值,不方便使用错误码方式处理。比如 T& operator这样的函数,如果pos越界了只能使用异常或者终止程序处理,没办法通过返回值表示错误

C++异常的缺点

  1. 异常会导致程序的执行流乱跳,并且非常的混乱,并且是运行时出错抛异常就会乱跳。这会导致我们跟踪调试时以及分析程序时,比较困难。
  2. 异常会有一些性能的开销。当然在现代硬件速度很快的情况下,这个影响基本忽略不计。
  3. C++没有垃圾回收机制,资源需要自己管理。有了异常非常容易导致内存泄漏、死锁等异常安全问题。这个需要使用RAII来处理资源的管理问题。学习成本较高。
  4. C++标准库的异常体系定义得不好,导致大家各自定义各自的异常体系,非常的混乱。
  5. 异常尽量规范使用,否则后果不堪设想,随意抛异常,外层捕获的用户苦不堪言。所以异常规范有两点:
    1. 抛出异常类型都继承自一个基类;
    2. 函数是否抛异常、抛什么异常,都使用 func() throw();的方式(或者C++11的noexcept)规范化。

总结:异常总体而言,利大于弊,所以工程中我们还是鼓励使用异常的。另外OO的语言基本都是用异常处理错误,这也可以看出这是大势所趋


本节完…

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

95后女孩穿旗袍走红,老手艺在淘宝迎来不一样的改变

在淘宝上有很多特色店铺&#xff0c;95后少女曾四斤喊出了“穿四斤&#xff0c;瘦10斤”的响亮口号&#xff0c;她在淘宝开设了一家“四斤自制”的少女旗袍店铺&#xff0c;全是30岁以下的年轻小姑娘在消费&#xff0c;“杨超越刚了穿了我们家旗袍录了拜年视频”&#xff0c;她…

员工福利?年轻女子穿着旗袍给程序员揉肩膀,这样真的合适吗

员工福利。有一个男子&#xff0c;他是一名程序员&#xff0c;上班的时候&#xff0c;在他身后站着一个女子&#xff0c;那个女子是专门为他服务的&#xff0c;这个福利让其他的同事羡慕不已。那个女子为什么专门为他服务&#xff1f;那个女子站在他的身后&#xff0c;给他按摩…

国潮迎春 百花旗放|2022东方丽人旗袍大赛暨国潮旗袍春晚华丽落幕

深圳商界讯&#xff1a;2021年12月4日&#xff0c;在深圳大鹏新区玫瑰庄园国际艺术中心&#xff0c;由深圳丽影文化主办&#xff0c;深圳前海卫视协办、深圳楚商国际俱乐部与商协汇总会、广州巽彩商贸、广东康尼蒂克等赞助的2022东方丽人旗袍大赛暨国潮旗袍春晚隆重举行。 本次…

Git 入门

一、版本控制 1.1 什么是版本控制 版本控制&#xff08;Revision control&#xff09;是一种在开发的过程中用于管理我们对文件、目录或工程等内容的修改历史&#xff0c;方便查看更改历史记录&#xff0c;备份以便恢复以前的版本的软件工程技术。简单说就是用于管理多人协同开…

[保研/考研机试] KY35 最简真分数 北京大学复试上机题 C++实现

题目链接&#xff1a; 最简真分数https://www.nowcoder.com/share/jump/437195121691719749588 描述 给出n个正整数&#xff0c;任取两个数分别作为分子和分母组成最简真分数&#xff0c;编程求共有几个这样的组合。 输入描述&#xff1a; 每组包含n&#xff08;n<600&…

online-shop项目相关

一、准备工作 1.vue启动相关 1.下载nodejs 2.进入vue项目文件夹cmd执行命令&#xff1a;npm install (安装环境运行需要的包)npm audit fix启动&#xff1a;npm run dev2.onlineshop 1.新建apps和extra_apps&#xff08;为了其他文件可以引入&#xff0c;创建python package…

Django(Celery+日志)

celery文档参考:http://docs.jinkan.org/docs/celery/ 同步请求&#xff1a;所有逻辑处理、数据计算任务在View中处理完毕后返回response。在View处理任务时用户处于等待状态&#xff0c;直到页面返回结果。异步请求&#xff1a;View中先返回response&#xff0c;再在后台处理…

新手遇到的问题之charles代理

标题新手遇到的问题之charles代理 首先确定手机和电脑在同一个wifi下&#xff0c;之后接着往下走 步骤一&#xff1a;下载charles安装包 步骤二&#xff1a;安装charles 步骤三&#xff1a;首先打开Charles设置Charles的proxy setting&#xff0c; port一般都默认8888&…

一只会鲤鱼打挺的咸鱼翻身历程——DeanDawn_

一只会鲤鱼打挺的咸鱼翻身历程 前言&#xff1a;本篇文章字数为6331&#xff0c;字数较多&#xff0c;建议慢慢品&#xff0c;看谁能get到其中的点(本篇比较杂乱&#xff0c;个人情感抒发较多&#xff0c;心灵鸡汤也是自己苦涩泪水伤感熬出来的。害&#xff0c;原创不易&#…

Charles工具疑难杂症汇总

Charles工具疑难杂症汇总 Charles是一款很好用的抓包工具&#xff0c;但是使用Charles时会遇到各种感觉很莫名其妙的状况&#xff0c;接下来就是针对各种问题给出解决方法~ 一、为什么用charles不能抓到https的包 解决方法&#xff1a; 1、查看是否已勾选ssl功能&#xff0…

Django(缓存系统)

什么是缓存Cache 缓存是一类可以更快的读取数据的介质统称&#xff0c;也指其它可以加快数据读取的存储方式。一般用来存储临时数据&#xff0c;常用介质的是读取速度很快的内存。一般来说从数据库多次把所需要的数据提取出来&#xff0c;要比从内存或者硬盘等一次读出来付出的…

【react】配置React 的开发环境

安装之前要确认你的机器上安装了 node.js 环境包括 npm。如果没有安装&#xff0c;可以到 node.js 的官网下载自己电脑的对应的安装包来安装好环境。Node.js 安装配置菜鸟教程 node自带npm 第一种方法&#xff08;create-react-app&#xff09; 安装好环境以后&#xff0c;…

Mellel 5 for mac(文字处理软件)

Mellel 5 for mac一款非常好用的文字处理软件&#xff0c;Mellel 5版包括所有经典的文本编辑工具&#xff0c;强大&#xff0c;灵活和可靠&#xff0c;它将帮助您撰写书籍&#xff0c;学术论文或博士学位论文&#xff0c;从概述想法到完成手稿。 Mellel 5 for mac安装教程 在本…

bliss android x86,Bliss OS现在可让您基于Android-x86和AOSP在PC上运行Android 10

Bliss OS是一个基于Android-x86项目的开源操作系统&#xff0c;有望让您在任何Linux&#xff0c;Windows或Chromebook PC或平板电脑设备上运行最新的Android 10移动操作系统。 Bliss OS基于AOSP(Android开放源代码项目)和Android-x86项目&#xff0c;提供了许多自定义和主题选项…

nginx跨域步骤详情

此文章只实现在本地开发环境下的应用nginx跨域 1.下载nginx稳定版本 下载地址&#xff1a;nginx: download 2. 配置nginx文件下的nginx.conf 3. 打包 我用的react &#xff0c;打包命令是&#xff1a;npm run bulid , 把打包后的dist文件下的内容复制到下面的文件地址 4…

index.html trend.html

1.jq22模板下载&#xff1a;http://www.jq22.com/jquery-info22538 2.layUI下载&#xff08;layUI-v2.5.5&#xff09;&#xff1a;https://www.layui.com/ 3.jquery下载&#xff08;Development 3.4.1&#xff09;&#xff1a;https://jquery.com/download/ 【网页直接打开文件…

Starlink星链计划能与5G抗衡?看一下马斯克吹过的牛

文章目录 一、Starlink星链计划是什么&#xff1f;1. 卫星发射情况2. 性能测试 二、5G 通信性能1. 通信速度2.通信时延3. 速度快的主要原因4. 系统容量 三、Starlink 与 5G 的对比1. 覆盖范围2. 通信速度 四、Starlink 的优势1. 局部地区的网络服务2. 军事服务3. 未来远景 一、…

docker基础使用

docker下载centos镜像(用作配置jdk环境系统) docker pull centos #版本号可以自己加,默认拉取最新的 docker命令 docker search xxxx 搜索xxxx镜像 docker pull xxxx 下载xxxx镜像&#xff0c;版本号…

Keras--基于VGG16卷积神经网络---猫狗分类

Cats vs. Dogs&#xff08;猫狗大战&#xff09;来源于 Kaggle 上的一个竞赛&#xff0c;内容非常简单&#xff0c; Kaggle 提供了一个猫和狗的数据集&#xff0c;我们需要建立一个算法进行训练&#xff0c;最后这个算法要能准确识别出猫和狗。Kaggle 提供的数据集分为训练集…