C++|异常

目录

一、异常概念

二、异常使用

2.1异常的抛出与捕获

2.2异常的重新抛出

2.3异常安全注意事项

2.4异常规范

三、自定义异常体系 

四、C++标准库的异常体系

五、异常的优缺点


对于传统的错误处理机制,例如c语言常用的:

        1.assert,捕获到错误时,它会直接终止程序。带来的问题就是,对于一些小细节错误例如除0错误,而直接终止程序属实是大财小用

        2.返回错误码,需要根据错误码自己去查找相应的错误。

一、异常概念

异常是一种处理错误的方式,当遇到无法处理的错误,可以通过抛异常让函数的直接或间接调用者来捕获,其中涉及到几个关键词,throw,catch,try。

throw:当错误出现时,程序会抛出该错误。编译器会通过throw关键字来完成

catch:用于捕获异常,可以有多个catch进行捕获

try:try中的代码称为保护代码,出现错误时,才具有资格被抛异常

try/catch语句的语法如下:

try
{

        //保护的标识代码

}

catch(Exception e)
{

     //问题处理

}

二、异常使用

2.1异常的抛出与捕获

异常的抛出和匹配原则

①异常通过抛出对象而触发的,该对象的类型决定了被哪个catch捕获。 

#include <iostream>
using namespace std;


double Division(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
		throw "Division by zero condition!";//抛出的异常是字符串
	else
		return ((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)//捕获throw抛出的异常,因为throw抛出异常是一串字符串,可以被constchar*接收
	{
		cout << errmsg << endl;//输出异常内容
	}
	return 0;
}

输出结果: 

当分母是0时,就会抛出异常,就会被catch捕获,从而进入catch所在语块: 

当分母不是0时,正常执行代码,不会抛异常,就不会被catch所捕获,就会跳过catch:

如果catch中的类型与抛出对象的类型不一致,未被正确捕获,则会终止程序:

②被选中的处理代码是调用链中与该对象类型匹配且抛出异常位置最近的那一个

void Func() 
{
    int len, time;
    cin >> len >> time;
    try 
    {
        cout << Division(len, time) << endl;
    }
    catch (const char* errmsg) 
    {
        cout << "Caught in Func: " << errmsg << endl;
    }


}

int main() {
    try 
    {
        Func();
    }
    catch (const char* errmsg) 
    {
        cout << "Caught in main: " << errmsg << endl;
    }
    return 0;
}

其中被选中的处理代码指的是catch块。

调用链指的是(如图):

在函数调用链中匹配原则: 

1.首先检查throw本身是否在try块内部,如果是再查找匹配的catch语句。如果有匹配的则调到catch的地方进行处理。

2.没有匹配的catch则退出当前函数栈,继续在调用函数的栈中进行查找匹配的catch。

3.如果到达main函数的栈,依旧没有匹配的,则终止程序。所以实际中最后都要加一个catch(...)捕获任意类型的异常。

4.找到匹配的catch子句并处理以后,会继续执行后面代码知道main函数结束。

 说白了就是被抛出对象离哪个catch块最近,且对象类型匹配就被哪个catch捕获,且只捕获这一次,然后继续执行后面的代码知道main函数结束:

在上述的调用链中,会存在一个弊端,有人可能就会想, func中不捕获异常,由main来捕获,例如,将上述代码改造,只修改func部分:

void Func() 
{
    int* p = new int[1];
    int len, time;
    cin >> len >> time;

    cout << Division(len, time) << endl;

    delete[] p;

}

仔细观察,调用Division函数时,若抛出异常,最终是被main函数中的catch所捕获,直接进入到main函数中的catch中了,但是,delete[] p不会被执行了 ,导致的问题就是内存泄漏。所以解决的方案可以是带上try/catch。还可以使用日志记录错误。还有就是智能指针,这个等后面篇章会讲。

③抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch以后销毁。(这里的处理类似于函数的传值返回)

#include <iostream>
using namespace std;
double Division(int a, int b) {
    if (b == 0)
    {
        string str("Division by zero condition!");
        throw str;//str是一个局部对象,出了作用域会销毁,所以在销毁前,会拷贝给一个临时对象,再将该临时对象抛出去
    }
    else
    {
        return ((double)a / (double)b);

    }
}

void Func() 
{
    int len, time;
    cin >> len >> time;
    try 
    {
        cout << Division(len, time) << endl;
    }
    catch (const string& errmsg) //捕获抛出的临时对象后,临时对象销毁
    {
        cout << "Caught in Func: " << errmsg << endl;//输出异常内容
    }


}

int main() {
    try 
    {
        Func();
    }
    catch (const char* errmsg) 
    {
        cout << "Caught in main: " << errmsg << endl;
    }
    return 0;
}

输出结果:

④catch(...)可以捕获任意类型的异常,但带来的问题是不知道异常错误是什么。 

还是用最开始的代码,只需更改一下main函数:

int main()
{
	try 
	{
		Func();
	}
	catch (...)//虽然可以捕获任意类型,但是却不能知道捕获到类型是什么,也就不能知道内容是啥
	{
		cout << "unkown exception" << endl;
	}
	return 0;
}

 输出结果:

⑤实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象,使用基类捕获,这个在实际中非常实用,在后面会讲

2.2异常的重新抛出

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

#include <iostream>
using namespace std;
double Division(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
	{
		throw "Division by zero condition!";
	}
	return (double)a / (double)b;
}
void Func()
{
	// 这里可以看到如果发生除0错误抛出异常,另外下面的array没有得到释放。
	// 所以这里捕获异常后并不处理异常,异常还是交给外面处理,这里捕获了再
	// 重新抛出去。
	int* array = new int[10];
	try {
		int len, time;
		cin >> len >> time;
		cout << Division(len, time) << endl;
	}
	catch (...)//捕获Division函数中抛过来的异常
	{
		//将array所开空间释放
		cout << "delete []" << array << endl;
		delete[] array;
		throw;//将捕获的异常不处理,重新抛出去,即抛给了main函数中的catch
	}

}
int main()
{
	try
	{
		Func();
	}
	catch (const char* errmsg)//捕获Func函数中抛过来的异常
	{
		cout << errmsg << endl;
	}
	return 0;
}

输出结果:

2.3异常安全注意事项

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

2.4异常规范

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

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

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

// 这里表示这个函数会抛出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(...)捕获异常导致的问题是不知道异常内容,为了解决这一问题,实际当中,公司会自定义自己的一套异常体系,这个体系是一套继承的规范体系,那么大家抛出的都是派生类对象,并将派生类对象传给基类,捕获异常时就捕获父类就可以,然后通过父类的多态调用打印出异常内容,这种方法大大优化了异常的捕获。例如:

基类:

#include <iostream>
#include <Windows.h>
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;       //错误码
};

基类包含了两个信息,错误码和错误码对应的内容,通过这些信息就可以对症下药。

对于基类的继承,子类满足如下关系:

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 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();
		}
		catch (const Exception& e) // 这里捕获父类对象就可以
		{
			// 多态
			cout << e.what() << endl;
		}
		catch (...)
		{
			cout << "Unkown Exception" << endl;
		}
	}
	return 0;
}

 输出结果:

四、C++标准库的异常体系

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

说明:实际中我们可以可以去继承exception类实现自己的异常类。但是实际中很多公司像上面一样自己定义一套异常继承体系。因为C++标准库设计的不够好用。

int main()
{
    try{
        vector<int> v(10, 5);

        // 这里如果系统内存不够也会抛异常        
        v.reserve(1000000000);

        // 这里越界会抛异常
        v.at(10) = 100; 
    }
    catch (const exception& e) // 这里捕获父类对象就可以    
    {
        cout << e.what() << endl;
    }
    catch (...)
    {
        cout << "Unkown Exception" << endl;
    }
    return 0;
}

 输出结果:

五、异常的优缺点

C++异常的优点:

1. 异常对象定义好了,相比错误码的方式可以清晰准确的展示出错误的各种信息,甚至可以包含堆栈调用的信息,这样可以帮助更好的定位程序的bug。

2. 返回错误码的传统方式有个很大的问题就是,在函数调用链中,深层的函数返回了错误,那么我们得层层返回错误,最外层才能拿到错误,具体看下面的详细解释。

int ConnnectSql()
{
	// 用户名密码错误
	if (...)
		return 1;

	// 权限不足
	if (...)
		return 2;
}

int ServerStart() {
	if (int ret = ConnnectSql() < 0)
		return ret;
	int fd = socket()
		if(fd < 0)
		return errno;
}

int main()
{
	if (ServerStart() < 0)
		...

		return 0;
}

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

也需要使用异常。

4. 部分函数使用异常更好处理,比如构造函数没有返回值,不方便使用错误码方式处理。比如T& operator这样的函数,如果pos越界了只能使用异常或者终止程序处理,没办法通过返回值表示错误。

C++异常的缺点:

1. 异常会导致程序的执行流乱跳,并且非常的混乱,并且是运行时出错抛异常就会乱跳。这会

导致我们跟踪调试时以及分析程序时,比较困难。

2. 异常会有一些性能的开销。当然在现代硬件速度很快的情况下,这个影响基本忽略不计。

3. C++没有垃圾回收机制,资源需要自己管理。有了异常非常容易导致内存泄漏、死锁等异常

安全问题。这个需要使用RAII来处理资源的管理问题。学习成本较高。

4. C++标准库的异常体系定义得不好,导致大家各自定义各自的异常体系,非常的混乱。

5. 异常尽量规范使用,否则后果不堪设想,随意抛异常,外层捕获的用户苦不堪言。所以异常规范有两点:一、抛出异常类型都继承自一个基类。二、函数是否抛异常、抛什么异常,都使用 func() throw();的方式规范化。

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

end~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【环境准备】 Vue环境搭建

文章目录 前言vue-cli 安装创建项目3.0、以下3.0 、以上 前言 书接上回《NodeJs(压缩包版本)安装与配置》&#xff0c;安装完了NodeJs&#xff0c;接下来就要配置vue的环境了。 vue-cli 安装 安装vue-cli输入如下命令 #&#xff08;安装的是最新版&#xff09; npm install …

windows的远程桌面连接docker

1. Docker容器中运行远程桌面服务 (RDP)&#xff1a;您的Docker容器需要安装和运行远程桌面服务。通常&#xff0c;远程桌面服务在Windows操作系统上可用。如果您使用的是Linux容器&#xff0c;则需要安装一个支持RDP协议的桌面环境和RDP服务器。 2. 开放RDP端口&#xff1a;通…

比赛获奖的武林秘籍:05 电子计算机类比赛国奖队伍技术如何分工和学习内容

比赛获奖的武林秘籍&#xff1a;05 电子计算机类比赛国奖队伍技术如何分工和学习内容 摘要 本文主要介绍了在电子计算机类比赛中技术层面上的团队分工和需要学习的内容&#xff0c;分为了嵌入式硬件、嵌入式软件、视觉图像处理、机械、上位机软件开发和数据分析等六个方向&am…

Mybatis Plus 3.X版本的insert填充自增id的IdType.ID_WORKER策略源码分析

总结/朱季谦 某天同事突然问我&#xff0c;你知道Mybatis Plus的insert方法&#xff0c;插入数据后自增id是如何自增的吗&#xff1f; 我愣了一下&#xff0c;脑海里只想到&#xff0c;当在POJO类的id设置一个自增策略后&#xff0c;例如TableId(value "id",type …

展开说说:Android服务之实现AIDL跨应用通信

前面几篇总结了Service的使用和源码执行流程&#xff0c;这里再简单分析一下如果需要Service跨进程通信该怎样做。AIDL&#xff08;Android Interface Definition Language&#xff09;Android接口定义语言&#xff0c;用于实现 Android 两个进程之间进行进程间通信&#xff08…

计算机网络之WPAN 和 WLAN

上一篇文章内容&#xff1a;无线局域网 1.WPAN&#xff08;无线个人区域网&#xff09; WPAN 是以个人为中心来使用的无线个人区域网&#xff0c;它实际上就是一个低功率、小范围、低速率和低价格的电缆替代技术。 &#xff08;1&#xff09; 蓝牙系统(Bluetooth) &#…

新闻资讯整合平台:一站式满足企业信息需求

摘要&#xff1a; 面对信息爆炸的时代&#xff0c;企业如何在海量数据中快速获取有价值资讯&#xff0c;成为提升竞争力的关键。本文将探讨如何通过一站式新闻资讯整合平台&#xff0c;实现企业信息需求的全面满足&#xff0c;提升决策效率&#xff0c;同时介绍实用工具推荐&a…

开源数据科学平台Anaconda简介

开源数据科学平台Anaconda简介 零、时光宝盒 最近&#xff0c;某金融行业女性选择以跳楼的形式结束自己的生命&#xff0c;这件不幸的事情成了热门话题&#xff0c;各种猜测的都有&#xff0c;有些人评论的话真的很过分。我想起前段时间看到的&#xff0c;有个女学生跳江&#…

ISO/OSI七层模型

ISO:国际标准化/ OSI:开放系统互联 七层协议必背图 1.注意事项&#xff1a; 1.上三层是为用户服务的&#xff0c;下四层负责实际数据传输。 2.下四层的传输单位&#xff1a; 传输层&#xff1b; 数据段&#xff08;报文&#xff09; 网络层&#xff1a; 数据包&#xff08;报…

git撤销/返回到某次提交(idea工具 + gitbush)

不多说废话&#xff0c;直接展示使用。 方法一&#xff1a;使用idea工具进行返回 准备某次过度提交 使用idea打开git log 找到要回去的版本 点击右键选到reset 模式选hard&#xff0c;强制回滚 这个时候本地代码已经回归你指定的版本了。 这个时候再进行强制推送&#xff0c…

读书笔记-Java并发编程的艺术-第4章(Java并发编程基础)-第3节(线程间通信)

文章目录 4.3 线程间通信4.3.1 volatile和synchronized 关键字4.3.2 等待/通知机制4.3.3 等待/通知的经典范式4.3.4 管道输入 / 输出流4.3.5 Thread.join()的使用4.3.6 ThreadLocal的使用 4.3 线程间通信 线程开始运行&#xff0c;拥有自己的栈空间&#xff0c;就如同一个脚本…

APP项目测试 之 APP性能测试

性能指标描述&#xff1a;一定是某种时间内某种条件执行某种操作&#xff0c;性能指标如何&#xff1f; 性能测试可以考虑和稳定性结合&#xff0c;monkey测试时使用性能监控工具监控性能数据。 例如: 2小时内持续刷新操作,性能如何? 持续运行8小时,性能如何&#xff1f; 常见…

【MySQL】详解

SQL语句的分类&#xff1a; 1.DDL&#xff08;Data Definition Languages&#xff09;语句&#xff1a; 数据定义语言 &#xff0c;这些语句定义了不同的数据段&#xff0c;数据库&#xff0c;表&#xff0c;列&#xff0c;索引等数据库对象的定义。常用的语句关键字主要包括…

随笔(一)

1.即时通信软件原理&#xff08;发展&#xff09; 即时通信软件实现原理_即时通讯原理-CSDN博客 笔记&#xff1a; 2.泛洪算法&#xff1a; 算法介绍 | 泛洪算法&#xff08;Flood fill Algorithm&#xff09;-CSDN博客 漫水填充算法实现最常见有四邻域像素填充法&#xf…

Studio One直播声音怎么调 Studio One直播没有声音输出怎么办 studio one如何设置声音变好听

Studio One做为新生代音乐工作站&#xff0c;凭借更低的价格和完备的功能&#xff0c;获得了音乐人和直播行业工作者的青睐&#xff0c;尤其是对硬件声卡的适配支持更好&#xff0c;特别适合用来配合线上教学和电商带货。 一、Studio One直播声音怎么调 在Studio One进行直播时…

AdaBoost集成学习算法理论解读以及公式为什么这么设计?

本文致力于阐述AdaBoost基本步骤涉及的每一个公式和公式为什么这么设计。 AdaBoost集成学习算法基本上遵从Boosting集成学习思想&#xff0c;通过不断迭代更新训练样本集的样本权重分布获得一组性能互补的弱学习器&#xff0c;然后通过加权投票等方式将这些弱学习器集成起来得到…

P8306 【模板】字典树

题目描述 给定 n 个模式串 s1​,s2​,…,sn​ 和 q 次询问&#xff0c;每次询问给定一个文本串 ti​&#xff0c;请回答 s1​∼sn​ 中有多少个字符串 sj​ 满足 ti​ 是 sj​ 的前缀。 一个字符串 t 是 s 的前缀当且仅当从 s 的末尾删去若干个&#xff08;可以为 0 个&#…

Scissor算法-从含有表型的bulkRNA数据中提取信息进而鉴别单细胞亚群

在做基础实验的时候&#xff0c;研究者都希望能够改变各种条件来进行对比分析&#xff0c;从而探索自己所感兴趣的方向。 在做数据分析的时候也是一样的&#xff0c;我们希望有一个数据集能够附加了很多临床信息/表型&#xff0c;然后二次分析者们就可以进一步挖掘。 然而现实…

【深度学习基础】MacOS PyCharm连接远程服务器

目录 一、需求描述二、建立与服务器的远程连接1. 新版Pycharm的界面有什么不同&#xff1f;2. 创建远程连接3. 建立本地项目与远程服务器项目之间的路径映射4.设置保存自动上传文件 三、设置解释器总结 写在前面&#xff0c;本人用的是Macbook Pro&#xff0c; M3 MAX处理器&am…

【Linux】多线程_2

文章目录 九、多线程2. 线程的控制 未完待续 九、多线程 2. 线程的控制 主线程退出 等同于 进程退出 等同于 所有线程都退出。为了避免主线程退出&#xff0c;但是新线程并没有执行完自己的任务的问题&#xff0c;主线程同样要跟进程一样等待新线程返回。 pthread_join 函数…