C++学习进阶:异常

目录

1.异常处理机制

1.1.抛异常和捕获异常

1.1.1.异常机制的基本场景

1.1.2.函数调用中异常栈展开的匹配规则:

1.2.异常机制的实际应用场景 

2.异常相关知识

2.1.异常安全和异常重新抛出

2.2.noexcept关键字

2.3.异常的优缺点 


1.异常处理机制

我们在C语言的学习中,了解了C语言的错误处理机制是通过终止程序和返回错误码这两种形式。 

而对于C++,是通过使用“异常”这一种错误处理机制,它允许程序在运行时遇到错误时,能够以一种结构化和可预测的方式进行处理。异常处理机制包括:抛出异常(throw)、捕获异常(catch)

1.1.抛异常和捕获异常

抛出异常

当程序遇到无法处理的错误时,可以使用throw关键字抛出一个异常。抛出异常时,可以传递一个值,这个值可以是任何类型的对象,包括基本数据类型和自定义类型。

捕获异常

catch块用于捕获异常。当异常被抛出时,控制流会立即离开当前函数(或代码块),并寻找能够处理该异常的catch块。一旦找到匹配的catch块,异常的值就会被传递给该块,并且该块中的代码会被执行。

1.1.1.异常机制的基本场景

抛出异常需要和捕获异常成对出现,接下来我们用数组越界的场景来学习异常机制!

#include<iostream>
#include<windows.h>
using namespace std;

int Exception(int i)
{

	int arr[5] = { 0, 1, 2, 3, 4 };
	if (i >=5)
	{
		throw "数组越界";
	}
	else
	{
		return arr[i];
	}
}
void Print()
{
	int i = 0;
    cout<<"需要打印下标为几的数组内容<<endl;
	cin >> i;
	cout << Exception(i) << endl;
}
int main()
{
    while(1)
    {
        sleep(2000);
        // try catch模块捕捉异常
	    // 异常一定要被捕获,不然会退出
	    try
	    {
	    	Print();
	    }
	    catch (const char* s)
	    {
	        cout << s << endl;
	    }
	    // 进入该区域表示 有人没有按照规范抛异常
	    // 可以捕获任意类型的内容
	    catch (...)
	    {
	    	cout << "未知异常" << endl;
	    }
    }
}

 这个就是异常最基本的一个场景,这里我们需要注意:

  1. 抛出异常后需要有接收异常的模块,不然会造成程序崩溃
  2. 抛出异常的类型需要和接收异常的类型一致,因为异常是通过抛出对象引发的,所以我们需要进行类型的匹配,传回匹配的异常信息
  3. 我们需要用catch(...)来作为没有按照规范的异常的保险,防止程序崩溃

当我们修改一下,抛异常的代码,改为抛出string对象。(注意catch处需要重载一个string类型)

int Exception(int i)
{
	int arr[5] = { 0, 1, 2, 3, 4 };
	if (i >=5)
	{
		string s = "数组越界";
		throw s;
	}
	else
	{
		return arr[i];
	}
}

这时我们抛出的对象的生命周期当前的Exception。catch接收时需要生成一个拷贝对象,这个对象在接收成功后销毁。我们可以使用右值引用语法来实现移动构造!(类似函数传值返回)


1.1.2.函数调用中异常栈展开的匹配规则:

  1. 首先检查throw是否在try内部,如果在的话就表示需要有匹配的catch,并且转到匹配的catch进行异常处理
  2. 如果当前没有匹配的catch就退出当前函数栈,往调用函数的栈中查找匹配的catch
  3. 如果到达main函数栈时,仍没有匹配的catch,程序将被中止,所以需要在main函数区添加catch(...)作为程序终止的保险,尽量只在main中添加
  4. 进入匹配的catch语句后,会执行catch语句的内容,并跳出try catch
#include<iostream>
#include<string.h>
using namespace std;

int Exception(int i)
{

	int arr[5] = { 0, 1, 2, 3, 4 };
	if (i >=5)
	{
		string s = "数组越界";
		throw s;
	}
	else
	{
		return arr[i];
	}
}
void Print()
{
	try 
	{
		int i = 0;
		cin >> i;
		cout << Exception(i) << endl;
	}
	catch (const string s)
	{
		cout << s << endl;
		cout << "aaaaaaa" << endl;
	}
}

int main()
{
	// 没有异常进入try
	try
	{
		Print();
	}
	// 异常一定要被捕获,不然会退出
	catch (const string s)
	{
		cout << s << endl;
		cout << "aaaaaaa" << endl;
	}
	// 进入该区域表示 有人没有按照规范抛异常
	// 可以捕获任意类型的内容
	catch (...)
	{
		cout << "未知异常" << endl;
	}
}

1.2.异常机制的实际应用场景 

实际开发企业级应用时,我们需要多人协作,那么避免不了出现异常捕获类型、以及catch内容不统一的问题,而这些问题可能会导致程序出现乱七八糟的结果。我们上面提到过:我们可以通过设置抛出异常的类型为某个对象,接收时通过对象来接受。这样我们可以结合类与对象的知识,通过设置一个Exception的异常基类,当不同的人需要抛异常时,就继承这个基类,实现一个派生类,抛出派生类的异常对象,和catch基类对象来实现。

#pragma once
#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 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(2000);

		try
		{
			HttpServer();
		}
		catch (const Exception& e) // 这里捕获父类对象就可以
		{
			// 但是实际情况,捕获到异常不一定就直接打印异常信息
			// 可能我们会进行重新调用原函数
			// 比如网络卡了,我们一般进行重新加载,而不是直接表示网络卡了	        
			
			// 进行若干次重试
			
			//int retry = 5;
			//while (e._id == 10 && retry != 0)
			//{
			//	HttpServer();
			//	retry--;
			//}
		

			// 多态,当我们接收不同派生类对象时,调用各自的what函数
			cout << e.what() << endl;
		}
		catch (...)
		{
			cout << "Unkown Exception" << endl;
		}
	}
}


代码解析:

  1. 首先基类定义一个虚函数what,负责返回错误信息
  2. 其余派生类都继承这个what函数,并实现各自代码逻辑的重写
  3. 通过几个模拟的场景,来抛出派生类对象构造的异常,并传入给各自的派生类,最终在不断的while循环模拟中,打印1.中的错误描述信息

2.异常相关知识

2.1.异常安全和异常重新抛出

我们先来看一个异常执行的场景:这里我们修改了Print函数内部的逻辑

#include<iostream>
#include<string.h>
using namespace std;

int Exception(int i)
{

	int arr[5] = { 0, 1, 2, 3, 4 };
	if (i >=5)
	{
		throw "数组越界";
	}
	else
	{
		return arr[i];
	}
}
void Print()
{
    int* array = new int[10];

	int i = 0;
	cin >> i;
	cout << Exception(i) << endl;

    delete[] array;
    cout<<"释放array资源"<<endl;
}

int main()
{
	try
	{
		Print();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	catch (...)
	{
		cout << "未知异常" << endl;
	}
}

在这段代码中,我们new了一个int* array,并且在Exception执行后的代码处才进行修改。这里会出现一个问题:当我们正常执行时,array能够被释放,当我们抛异常时,这个模块的代码会跳转到main中,而不会进行释放array的操作。

直接抛异常这个操作在某些场景会出现内存泄漏问题!!!

那么我们如何解决这种异常安全问题呢? 这时我们进行异常的重新抛出!

void Print()
{
    int* array = new int[10];

    try
    {
        int i = 0;
    	cin >> i;
	    cout << Exception(i) << endl;
    }
    //catch (const char* errmsg)
    //{
    //	delete[] array;
    //	cout << "释放array资源" << endl;
    //	// 再次抛出异常
    //	throw errmsg;
    //}
    
    // 实际场景会出现多种异常,所以这种写法更好,
    // 即使这里不知道类型,但传入到main就可以被catch写入的不同类型接受
    catch(...)
    {
        delete[] array;
        cout << "释放array资源" << endl;
        throw;
    }
}

我们抛出的异常信息,首先先背Print模块中的catch接收,然后再抛出,直到被main函数栈中的catch接收,实现重新抛出的接收! 

当我们抛异常时涉及到资源释放时,我们进行异常的重新抛出,来解决异常的安全问题

2.2.noexcept关键字

noexcept是 C++11 提供的一个新特性,用于指明某个函数或函数模板不会抛出异常。这个关键字提供了一种方式,让程序员能够明确地声明一个函数是否可能抛出异常,从而帮助编译器进行更好的优化,并减少因异常传播而导致的性能开销。 

void safeFunc() noexcept 
{  
    // 这个函数承诺不会抛出异常  
    // ...  
}

添加noexcept关键字,表示这个函数经过人为检查后定性为不会出现异常,然后编译器读取后就给他打上不出异常的标志,后续运行时不会检查是否出现异常。所以noexcept关键字不能用于会出现异常的函数中,这样子会导致编译器跳过对该函数异常的检查,使得异常抛出却没被接收,进而导致程序终止。

对于可能出现异常的函数我们不能添加noexcept关键字!!!


那么为什么我们需要noexcept这个关键字呢?

  1. 在异常处理机制引入C++时,我们一般是主动地定义某个函数会抛出什么种类的异常,就是将noexcept对应位置替换为throw(int),表示会抛出int类型的异常
  2. 但是实际应用时,这个代码规范没什么人愿意遵守,并且某些函数模块抛出的异常种类可能较多,所以C++11就通过noexcept关键字来简化这个过程

另外noexcept关键字的引入也有如下好处:

  1. 性能优化:编译器可以针对 noexcept 函数进行特定的优化,因为它们不需要为可能的异常传播保留额外的栈空间或进行其他可能降低性能的操作。
  2. 更好的错误处理:通过明确标记哪些函数可能抛出异常,程序员可以更清晰地了解代码的异常安全性,并相应地设计错误处理策略。
  3. 更明确的错误处理:当函数被标记为 noexcept 时,它向使用该函数的代码明确传达了不会抛出异常的承诺,这有助于减少代码中的不确定性。 

2.3.异常的优缺点 

优点:

  1. 实现合理的异常判断,相比错误码的方式可以清晰准确的展示出错误的各种信息,甚至包括堆栈调用的信息,这样可以帮助更好的定位程序的bug。(这里因为错误码在函数调用链中,需要层层返回错误,直到最外层才能比对并得到错误信息)
  2. 通过catch可以直接跳到错误地方,直接读取错误信息,当调用链深时,不用层层返回
  3. 很多第三方库都使用了异常处理机制,我们也需要匹配
  4. 无返回值函数无法返回错误码,例如类与对象的构造函数,所以引入异常机制,有助于无返回值函数的错误信息输出

缺点:

  1. 异常的执行流容易乱跳,当我们抛出异常后,会直接跳转到catch处,当我们调试程序时,会很复杂
  2. 异常具有一定的性能上的开销,但是可以忽略不计
  3. C++没有垃圾回收机制,资源需要自行管理,可能出现内存泄漏、死锁等问题
  4. 异常需要规范使用,如果内部随意抛异常,外部很容易catch出现问题

 

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

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

相关文章

Finding a needle in Haystack: Facebook’s photo storage——论文泛读

OSDI 2010 Paper 分布式元数据论文阅读笔记整理 问题 到2010年为止&#xff0c;用户已经在Facebook上传了超过650亿张照片&#xff0c;对于每个上传的照片&#xff0c;Facebook生成并存储四个不同大小的图像&#xff0c;导致目前存储了超过2600亿张图片&#xff0c;相当于超过…

AQS(AbstractQueuedSynchronizer)队列同步器源码解读

&#x1f3f7;️个人主页&#xff1a;牵着猫散步的鼠鼠 &#x1f3f7;️系列专栏&#xff1a;Java全栈-专栏 &#x1f3f7;️个人学习笔记&#xff0c;若有缺误&#xff0c;欢迎评论区指正 目录 1. 前言 2. AOS、AQS、AQLS的区别 3. AQS的底层原理 3.1. 核心思想 3.2. 数…

Qt 项目文件(.pro)概述

Qt 项目pro文件 引言一、pro文件初探二、部分参数详解 引言 Qt工程项目由项目文件&#xff08;.pro&#xff09;进行管理。qmake使用文件中的信息生成Makefile&#xff0c;其中包含构建每个项目所需的所有命令。pro文件通常包含源文件和头文件的列表、常规配置信息以及任何特定…

ST-GCN模型详解(+openpose)

ST-GCN模型详解&#xff08;openpose&#xff09; 一、什么是ST-GCN呢 基于骨架的动作识别&#xff08;Skeleton-Based Action Recognition&#xff09;主要任务是从一系列时间连续的骨骼关键点&#xff08;2D/3D&#xff09;中识别出正在执行的动作。因为牵涉到骨骼框架这种…

CentOS7升级openssl

文章目录 一 系统环境二 操作步骤三 版本检查 一 系统环境 公司服务器等保要求&#xff0c;修复openssl的高危漏洞。 本机使用centos7.9系统&#xff0c;openssl版本是1.0.2k&#xff0c;计划升级到1.1.1q 在执行下列操作前&#xff0c;务必要打快照做好备份&#xff0c;以防升…

030——从GUI->Client->Server->driver实现对红外遥控的控制

目录 1、 解决韦东山老师irda模块中断申请失败的bug 2、 client添加处理程序 3、 添加服务器处理程序和驱动处理句柄 4、 处理数据读出不准确问题 5、 修改后的展示 1、 解决韦东山老师irda模块中断申请失败的bug irda需要通过中断来触发读操作&#xff0c;申请中断需要引…

Octopus v2:斯坦福的嵌入设备专用大模型

斯坦福大学推出了 Octopus v2&#xff0c;这是一种突破性的设备上语言模型&#xff0c;旨在解决与现有模型相关的延迟、准确性和隐私问题。 NSDT工具推荐&#xff1a; Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑…

源码篇--Nacos服务--中章(1):Nacos服务端的启动

文章目录 前言一、Nacos Console 启动入口&#xff1a;二、启动过程&#xff1a;2.1 容器启动监听器&#xff1a;2.1.1 调整启动标识为正在启动状态&#xff1a;2.1.2 环境准备阶段&#xff1a;2.1.3 容器环境准备&#xff1a;2.1.4 自定义的环境变量 设置&#xff1a;2.1.5 服…

Spectre-v2 以及 Linux Retpoline技术简介

文章目录 前言一、Executive Summary1.1 Spectre-v2: Branch Predictor Poisoning1.2 Mitigating Spectre-v2 with Retpolines1.3 Retpoline Concept 二、BackgroundExploit Composition 三、(Un-)Directing Speculative Execution四、Construction (x86)4.1 Speculation Barri…

测试人员通常遇到的“坑”

网上看到一个帖子&#xff0c;从事多年的测试从业者&#xff0c;吐槽测试过程中遇到的“坑”&#xff0c;感觉比较有意思&#xff0c;我在工作当中也遇到通常的问题&#xff0c;看得出这位网友比较喜欢总结&#xff0c;帖子地址奉上&#xff0c;有兴趣的可以浏览一下&#xff1…

bug(警告):[vue-router] Duplicate named routes definition: …

查看警告&#xff1a;[vue-router] Duplicate named routes definition——翻译[vue-router]重复命名路由定义 小编劝诫&#xff1a;当我们在开发过程中警告也一定不要忽略&#xff0c;虽然你在本地跑代码时这些警告影响项目的正常运行&#xff0c;但是会让你产生误区&#xff…

大模型日报|今日必读的8篇大模型论文

大家好&#xff0c;今日必读的大模型论文来啦&#xff01; 1.EdgeFusion&#xff1a;端侧文本到图像生成&#xff0c;只需不到一秒 用于文本到图像生成的稳定扩散&#xff08;SD&#xff09;技术需要大量计算&#xff0c;这对其实际应用构成了重大障碍。为此&#xff0c;最近…

Oracle進階SQLDay03

一、函數進階復習 1、行轉列 select 用水儿量&#xff08;噸&#xff09; 统计项, sum(case when t_account.month01 then USENUM end) 一月, sum(case when t_account.month02 then USENUM end) 二月, sum(case when t_account.month03 then USENUM end) 三月, sum(case when …

STM32学习和实践笔记(15):STM32中断系统

中断概念 CPU执行程序时&#xff0c;由于发生了某种随机的事件(外部或内部)&#xff0c;引起CPU暂 时中断正在运行的程序&#xff0c;转去执行一段特殊的服务程序(中断服务子程序 或中断处理程序)&#xff0c;以处理该事件&#xff0c;该事件处理完后又返回被中断的程序 继…

飞桨Ai(二)paddle使用CPU版本可以正常识别,切换为GPU版本时无法识别结果

一、问题描述&#xff1a; 刚开始用paddle的CPU版本&#xff0c;对训练好的模型进行推理&#xff0c;正常识别出想要的结果后来尝试使用paddle的GPU版本&#xff0c;然后发现识别出来是空的 二、系统思路&#xff1a; 最终系统环境如下&#xff1a; 系统&#xff1a;win10 …

有哪些公认好用且免费的云渲染网渲平台?渲染100邀请码1a12

现在云渲染是越来越火了&#xff0c;无论是在建筑设计、影视动画还是效果图行业都有它的身影&#xff0c;云渲染能缩短制作周期&#xff0c;提高工作效率&#xff0c;那么市面上有哪些公认好用且免费的云渲染平台呢&#xff1f;这次我们来了解下。 首先&#xff0c;我们来看看有…

vulfocus靶场tomcat-cve_2017_12615 文件上传

7.0.0-7.0.81 影响版本 Windows上的Apache Tomcat如果开启PUT方法(默认关闭)&#xff0c;则存在此漏洞&#xff0c;攻击者可以利用该漏洞上传JSP文件&#xff0c;从而导致远程代码执行。 Tomcat 是一个小型的轻量级应用服务器&#xff0c;在中小型系统和并发访问用户不是很多…

「GO基础」在Windows上配置VS Code GO语言开发环境

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

查看linux的主机配置脚本

废话不说 直接上指令 curl -Lso- bench.sh | bash 等待后&#xff0c;结果如图&#xff1a; 使用后没有问题&#xff0c;看情况使用 出事概不负责 介意勿用&#xff01;&#xff01;&#xff01;

LD-Pruner、EdgeFusion(On-Device T2I)、FreeDiff、TextCenGen、MemLLM

本文首发于公众号&#xff1a;机器感知 https://mp.weixin.qq.com/s/KiyNfwYWU-wBiCO-hE9qkA 苏 The devil is in the object boundary: towards annotation-free instance segmentation using Foundation Models Foundation models, pre-trained on a large amount of data…