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

【C++】异常处理

目录

一、C语言中传统的异常处理方式:

二、C++中的异常处理方式:

三、异常的使用

1、关于抛出与捕获:

2、关于异常的抛出和匹配:

3、异常的重新抛出:

4、异常安全:

5、异常规范:

四、异常的优缺点:

1、优点:

2、缺点:


一、C语言中传统的异常处理方式:

当C语言程序出现错误的时候会出现两种情况:

1、在程序结束时返回错误码(一般如果正常结束返回0)

2、在程序进行时直接发现错误直接进行终止(比如assert)

以上就是断言错误或者是错误码返回,

在断言错误中它会自己报出错具体位置和出错原因,就可以定位出错位置

注意:这是在debug的环境下才会有assert,如果在release版本会自动删除assert

二、C++中的异常处理方式:

在C++中,祖师爷觉得C语言的那些方式依然给的信息不够,所以就创造出来了抛异常,捕获异常这些概念,在C++中引入了三个关键字来进行关于程序异常的信息捕获。

1、try:

这个就是在try的代码段中进行监视的,如果有,就会进行抛出,捕获异常。

try 块中放置可能抛出异常的代码,try 块中的代码被称为保护代码。

try {//在这代码区域中进行监视,如果有异常就进行抛出
}
catch (const exception& e) {// 处理异常
}

2、throw:

这个就是抛出异常,

3、catch:

这个就是将抛出的异常捕获到

在try后面还可以进行多次catch的接收:

try
{//保护代码
}
catch (const exception& e)
{}
catch (const exception& e)
{}
catch (const exception& e)
{}

三、异常的使用

1、关于抛出与捕获:

double Division(int a, int b)
{if (b == 0)throw("被除数不能为零");elsereturn (float)a / (float)b;}void func()
{double x, y;cin >> x>> y;cout << Division(x,y) << endl;
}int main()
{try{func();}catch(const char* err){cout << err << endl;}return 0;
}

如果传输有问题的话就会catch,否则就正常返回

2、关于异常的抛出和匹配:

1、如果在不同函数中有多个匹配的,就会找离catch最近的那一层的catch,并且返回catch后,会继续执行catch后面的语句。

double Division(int a, int b)
{if (b == 0)throw("被除数不能为零");elsereturn (float)a / (float)b;}void func()
{double x, y;cin >> x>> y;try{cout << Division(x,y) << endl;}catch(const int* err){cout << "catch(const int* err)" << endl;cout << err << endl;}catch(const int err){cout << "catch(const int err)" << endl;cout << err << endl;}catch (const char* err){cout << "catch (const char* err)" << endl;cout << err << endl;}cout << "void func()" << endl;
}int main()
{try{func();}catch(const char* err){cout << err << endl;}return 0;
}

2、如果有多个catch就会找类型相同的catch语句进行执行,如果没有找到就会报错

3、那么就有一种方法:

catch(...)这样来进行捕获,这个是捕获任意类型的,作为程序异常捕获的最后一道防线,避免程序因为没有找到异常匹配而挂了

4、在抛出的过程中,如果跨越了多个函数后找到捕获了,就会直接跳转到对应地方,并不会一层一层地返回。

5、在实际情况中,异常并不是传这种内置类型的,而是通过派生类继承基类来传派生类这种的自定义类型的,所以实际中都会定义一套继承的规范体系。 这样大家抛出的都是继承的派生类对象,捕获一个基类就可以了

// 服务器开发中通常使用的异常继承体系
//作为父类
class Exception
{
public:Exception(const string& errmsg, int id):_errmsg(errmsg), _id(id){}virtual string what() const{return _errmsg;}
protected:string _errmsg;int _id;
};
//SQL模块的继承
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 = "SQL异常:";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 = "缓存异常:";str += _errmsg;return str;}
};
//Http模块的继承
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 = "HttpServer异常:";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){Sleep(500);try {HttpServer();CacheMgr();SQLMgr();}catch (const Exception& e) // 这里捕获父类对象就可以{// 多态cout << e.what() << endl;}catch (...){cout << "未知异常" << endl;}}return 0;
}

3、异常的重新抛出:

在抛出异常时,毕竟是直接跳转的,在中间省略的函数中可能会存在内存泄漏问题,如下,在func函数中,其开辟的数组就不会被释放。

//异常信息类
class Exception
{
public:Exception(int errcode, const string& content):_errno(errcode), _content(content){}string what() const{return to_string(_errno) + " : " + _content;}
public:int _errno = 0;//作为错误编号,在实际中方便查找string _content;//记录错误信息
};double Division(int a, int b)
{if (b == 0){Exception e(1, "被除数不能为0");throw e;}elsereturn (float)a / (float)b;}void func()
{double x, y;cin >> x>> y;int* arr = new int[10];cout << Division(x,y) << endl;delete[] arr;cout << "delete[] arr: " << arr << endl;cout << "void func()" << endl;
}int main()
{try{func();}catch(const Exception& err){cout << "int main()" << endl;err.what();}catch (...){cout << "int main()" << endl;cout << "未知错误" << endl;}return 0;
}

解决方法:

要想正确的释放内存,需要在func函数中主动捕获异常,将空间释放后,重新抛出异常,

所以func函数就需要改进为:

void func()
{int* arr = new int[10];try {int x, y;cin >> x >> y;cout << Division(x, y) << endl;}catch (...) {delete arr;throw;}
}

这里捕获异常后并不处理异常,异常还是交给main处理,这里捕获了再重新抛出去,

注意:这里捕获了什么类型的异常就抛出什么类型的异常。

4、异常安全:

1、构造函数完成对象的构造和初始化,最好不要在构造函数中抛出异常,否则可能导致对象不完整或没有完全初始化
2、析构函数主要完成资源的清理,最好不要在析构函数内抛出异常,否则可能导致资源泄漏

3、C++中异常经常会导致资源泄漏的问题,比如在new和delete中抛出了异常,导致内存泄漏,但是才C++11中引入了智能指针来解决这一问题,使用智能指针可以有效地避免因异常和忘记释放内存导致的资源泄漏

5、异常规范:

1、异常规格说明的目的是为了让函数使用者知道该函数可能抛出的异常有哪些。 可以在函数的 后面接throw(类型),列出这个函数可能抛掷的所有异常类型。

2、函数的后面接throw(),表示函数不抛异常。

3、若无异常接口声明,则此函数可以抛掷任何类型的异常。

void func1() throw(int, char, string); // 可能抛出这三种类型的异常void func2() throw(); // 该函数不会抛出异常void func3(); // 该函数可以抛出任何类型的异常

但是在C++11中引入了noexcept关键字,这样只需标记这个函数不会抛出异常,

但是如果对标记后的函数进行抛异常了,进程就会直接被终止。

四、异常的优缺点:

1、优点:

1、如果使用好了可以展示更多的错误信息,能够更好的定位bug

2、 返回错误码的传统方式有个很大的问题就是,在函数调用链中,深层的函数返回了错误,那么就得层层返回错误,最外层才能拿到错误,而如果在最外层捕获,异常就是直接返回到最外层,不用层层返回。

3、 很多的第三方库都包含异常,比如boost、gtest、gmock等等常用的库,那么我们使用它们也需要使用异常

4、部分函数使用异常更好表示错误,比如 T& operator[](size_t pos) 如果越界了就抛异常,而不是返回 T() 或 断言。

2、缺点:

1、在进行调试的时候代码流的跨度大,导致跟踪程序比较困难

2、因为捕获异常捕获的是一份临时拷贝,所以存在性能开销,但是现如今的设备几乎可以忽略不计

3、C++的标准库异常体系定义的不好,导致各自定义各自的比较混乱

4、异常尽量规范使用:

        a、抛出异常类型都继承自一个基类

        b、函数是否抛异常、使用关键字noexcept的方式规范化

但是异常总之是利大于弊的,在工程中也尽量按规范使用异常 

相关文章:

  • Android Stuido中编译信息出现乱码的解决方式
  • ClickHouse | 查询
  • C++ | Leetcode C++题解之第446题等差数列划分II-子序列
  • 最大正方形 Python题解
  • 第二十三节:学习拦截器或者使用AOP实现用户token参数请求检测(自学Spring boot 3.x的第六天)
  • IDEA几大常用AI插件
  • springboot+satoken实现刷新token(值变化)
  • STL之stackqueue篇(上)探索C++ STL中的Queue与Stack——构建数据处理的基础框架
  • django drf to_representation
  • NVIDIA Hopper 架构深入
  • 刷题学习日记 (1) - SWPUCTF
  • Python FFmpeg 安装使用教程
  • k8s 1.28.2 集群部署 ingress 1.11.1 包含 admission-webhook
  • 优化后的版本
  • 【Vue】vue2项目打包后部署刷新404,配置publicPath ./ 不生效问题
  • [译]Python中的类属性与实例属性的区别
  • 《网管员必读——网络组建》(第2版)电子课件下载
  • 【391天】每日项目总结系列128(2018.03.03)
  • HTTP中的ETag在移动客户端的应用
  • iBatis和MyBatis在使用ResultMap对应关系时的区别
  • Java 9 被无情抛弃,Java 8 直接升级到 Java 10!!
  • java多线程
  • JS字符串转数字方法总结
  • NLPIR语义挖掘平台推动行业大数据应用服务
  • PHP面试之三:MySQL数据库
  • Redis 懒删除(lazy free)简史
  • spring boot 整合mybatis 无法输出sql的问题
  • vuex 学习笔记 01
  • Vue学习第二天
  • vue学习系列(二)vue-cli
  • 第三十一到第三十三天:我是精明的小卖家(一)
  • 订阅Forge Viewer所有的事件
  • 猫头鹰的深夜翻译:JDK9 NotNullOrElse方法
  • 入手阿里云新服务器的部署NODE
  • 为视图添加丝滑的水波纹
  • 在GitHub多个账号上使用不同的SSH的配置方法
  • 完善智慧办公建设,小熊U租获京东数千万元A+轮融资 ...
  • 小白应该如何快速入门阿里云服务器,新手使用ECS的方法 ...
  • 正则表达式-基础知识Review
  • ​queue --- 一个同步的队列类​
  • #LLM入门|Prompt#2.3_对查询任务进行分类|意图分析_Classification
  • #职场发展#其他
  • (13)Hive调优——动态分区导致的小文件问题
  • (145)光线追踪距离场柔和阴影
  • (C#)一个最简单的链表类
  • (CVPRW,2024)可学习的提示:遥感领域小样本语义分割
  • (floyd+补集) poj 3275
  • (剑指Offer)面试题41:和为s的连续正数序列
  • (十七)Flink 容错机制
  • (十三)Java springcloud B2B2C o2o多用户商城 springcloud架构 - SSO单点登录之OAuth2.0 根据token获取用户信息(4)...
  • (四)鸿鹄云架构一服务注册中心
  • (转载)利用webkit抓取动态网页和链接
  • **登录+JWT+异常处理+拦截器+ThreadLocal-开发思想与代码实现**
  • *Algs4-1.5.25随机网格的倍率测试-(未读懂题)
  • .NET 8 跨平台高性能边缘采集网关