[cpp进阶]C++异常

news2024/10/5 19:15:41

文章目录

  • C语言传统处理错误的方式
  • C++异常概念
  • C++异常使用
    • 异常的抛出和捕获
    • 异常的重新抛出
    • 异常安全
    • 异常规范
  • 自定义异常体系
  • C++标准库的异常体系
  • 异常的优缺点

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

传统的错误处理机制:

  1. 终止程序。assert断言直接终止程序。缺点:过于粗暴,用户难以接受。比如发生内存错误,遇到除0错误就会直接终止程序。
  2. 返回错误码。缺点:需要程序员自己寻找对应的错误,调试成本较大。比如系统的很多库的接口函数都是通过把错误码放到errno中,表示错误。
  3. C 标准库中setjmp和longjmp组合。

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


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 块
}

C++异常使用

异常的抛出和捕获

异常的抛出和匹配原则:

  1. 异常是通过抛出对象引发的,对象的类型决定了执行哪一个catch块处理代码。
  2. 被执行的catch块执行代码是调用链中与抛出的异常对象类型匹配且离异常位置最近的那一个。
  3. 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,所以会生成一个拷贝对象,这个拷贝的临时对象会在执行catch处理代码以后销毁,这里的处理类似于函数的传值返回。
  4. 实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象,使用基类捕获,利用了"切片"原理
  5. catch(…)可以捕获任意类型的异常,如果不明确异常对象是什么类型,就可以使用catch(…)。

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

  1. 首先检查throw异常是否在try保护代码内部,如果在try块内部,则查找与throw抛出异常的对象类型匹配的catch代码,如果有匹配,则执行该catch块处理代码。
  2. 如果没有匹配的catch则退出当前函数栈帧,继续在调用该函数的栈帧中寻找匹配的catch。
  3. 如果到达main函数栈帧仍然没有找到匹配的catch代码块,则终止程序。 上述沿着调用链查找匹配catch代码的过程叫做栈展开。所以在实际中最后需要加一个catch(…)捕获任意类型的异常对象,否则如果有异常没有捕获,就会终止程序。
  4. 找到匹配的catch代码并处理后,会沿着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 Func1()
{
	int len, time;
	cin >> len >> time;
	cout << Division(len, time) << endl;
}

int main()
{
	try
	{
		Func1();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	catch (...)
	{
		cout << "unknown error" << endl;
	}

	return 0;
}

实验结果:

在这里插入图片描述
在这里插入图片描述


异常的重新抛出

有可能单个的catch不能完全处理一个异常,在进行一些校正处理以后,希望再交给更外层的调用链函数来处理,catch则可以通过重新抛出将异常传递给更上层的函数进行处理。或者在抛异常之前程序申请资源或者打开了文件,但是在抛异常之后退出当前栈,导致资源没有被释放,文件没有被关闭,这就需要在当前栈捕捉异常再重新抛出。

实验代码:

double Division(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
		throw "Division by zero condition!";
	else
		return ((double)a / (double)b);
}

void Func1()
{
	int* ptr = new int[10];
	try {
		int len, time;
		cin >> len >> time;
		cout << Division(len, time) << endl;
	}
	catch (...) {
		cout << "delete: " << ptr << endl;
		delete[] ptr;

		throw;  // 异常重新抛出
	}
}

int main()
{
	try
	{
		Func1();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	catch (...)
	{
		cout << "unknown error" << endl;
	}

	return 0;
}

实验结果:

在这里插入图片描述


异常安全

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

异常规范

  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 new (std::size_t size, void* ptr) throw();

C++98异常规范:
在这里插入图片描述
C++11异常规范:

在这里插入图片描述


自定义异常体系

在实际使用中,我们会自定义异常体系进行规范的异常管理,因为一个项目中如果大家随意抛异常,那么外层的调用者没有办法识别异常是从哪一个业务层抛出的,所以实际中都会定义一套继承的规范体系。这样大家抛出的都是继承的派生类对象,捕获一个基类就可以。

在这里插入图片描述

下面我们模拟通过基类对象捕捉各个应用层的派生类异常对象:

  1. 生成随机数种子,模拟现实中业务异常的随机性,实现基类Execption
  2. 如果随机数是3或4的倍数,HttpServer层抛出派生类HttpServerExecption异常对象
  3. 如果随机数是5或6的倍数,Cache层抛出派生类CacheExecption异常对象
  4. 如果随机数是7的倍数,SQL层抛出派生类SqlExecption异常对象
class Execption
{
public:
	Execption(const string& errmsg, int id)
		:_errmsg(errmsg)
		, _id(id)
	{}

	virtual string what() const
	{
		return _errmsg;
	}

protected:
	string _errmsg;
	int _id;
};

class HttpServerExecption : public Execption
{
public:
	HttpServerExecption(const string& errmsg, int id, string type)
		:Execption(errmsg, id)
		, _type(type)
	{}

	virtual string what() const
	{
		string errmsg;
		errmsg += "HttpServerException: ";
		errmsg += _type;
		errmsg += ":";
		errmsg += _errmsg;
		return errmsg;
	}

private:
	string _type;
};

class CacheExecption : public Execption
{
public:
	CacheExecption(const string& errmsg, int id)
		:Execption(errmsg, id)
	{}

	virtual string what() const
	{
		string errmsg;
		errmsg += "CacheException: ";
		errmsg += _errmsg;
		return errmsg;
	}
};

class SqlExecption : public Execption
{
public:
	SqlExecption(const string& errmsg, int id, string sql)
		:Execption(errmsg, id)
		, _sql(sql)
	{}

	virtual string what() const
	{
		string errmsg;
		errmsg += "SqlException: ";
		errmsg += _errmsg;
		errmsg += " -> ";
		errmsg += _sql;
		return errmsg;
	}
private:
	string _sql;
};

void SqlMgr()
{
	srand(time(0));
	if (rand() % 7 == 0) {
		throw SqlExecption("权限不足", 100, "select * from table where name='jack'");
	}
}


void CacheMgr()
{
	srand(time(0));
	if (rand() % 5 == 0) {
		throw CacheExecption("权限不足", 100);
	}
	else if (rand() % 6 == 0) {
		throw CacheExecption("数据不存在", 101);
	}
	SqlMgr();
}


void HttpServer()
{
	srand(time(0));
	if (rand() % 3 == 0) {
		throw HttpServerExecption("请求资源不存在", 100, "get");
	}
	else if (rand() % 4 == 0) {
		throw HttpServerExecption("权限不足", 200, "post");
	}

	CacheMgr();
}

void ServerStart()
{
	while (true) {
		this_thread::sleep_for(chrono::seconds(1));

		try {
			HttpServer();
		}
		catch (const Execption& e) {
			cout << e.what() << endl;
		}
		catch (...) {
			cout << "Unknown error" << endl;
		}
	}
}


int main()
{
	ServerStart();

	return 0;
}

实验结果:

在这里插入图片描述


C++标准库的异常体系

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


异常的优缺点

C++异常的优点:

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

C++异常的缺点:

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

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

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

相关文章

Fiddler抓取手机APP报文

Http协议代理工具有很多&#xff0c;比如Burp Suite、Charles、Jmeter、Fiddler等&#xff0c;它们都可以用来抓取APP报文&#xff0c;其中charles和Burp Suite是收费的&#xff0c;Jmeter主要用来做接口测试&#xff0c;而Fiddler提供了免费版&#xff0c;本文记录一下在Windo…

位运算做加法,桶排序找消失元素,名次与真假表示,杨氏矩阵,字符串左旋(外加两道智力题)

Tips 1. 2. 3. 大小端字节序存储这种顺序只有在放进去暂时存储的时候是这样的&#xff0c;但是一旦我里面的数据需要参与什么运算之类的&#xff0c;会“拿出来”先恢复到原先的位置再参与运算&#xff0c;因此&#xff0c;大小端字节序存储的什么顺序不影响移位运算等等…

【案例教程】CLUE模型构建方法、模型验证及土地利用变化情景预测实践技术

【前沿】&#xff1a;土地利用/土地覆盖数据是生态、环境和气象等领域众多模型的重要输入参数之一。基于遥感影像解译&#xff0c;可获取历史或当前任何一个区域的土地利用/土地覆盖数据&#xff0c;用于评估区域的生态环境变化、评价重大生态工程建设成效等。借助CLUE模型&…

声音产生感知简记

声音产生 人的发音器官包括:肺、气管、声带、喉、咽、鼻腔、口腔、唇。肺部产生的气流冲击声带,产生震动。 声带每开启和闭合一次的时间是基音周期(Pitch period,T),其到数为基音频率(F.=1/T,基频),范围在70-450Hz。基频越高,声音越尖细,如小孩的声音比大人尖,就是…

编译错误2

本文迁移自本人网易博客&#xff0c;写于2015年11月25日&#xff0c;编译错误2 - lysygyy的日志 - 网易博客 (163.com)1、error C2059:语法错误&#xff1a;“<L_TYPE_RAW>”error C2238:意外的标记位于“;”之前.错误代码定位于&#xff1a;BOOL TreeView_GetCheckState…

excel函数公式:常用高频公式应用总结 上篇

公式1&#xff1a;条件计数条件计数在Excel的应用中十分常见&#xff0c;例如统计人员名单中的女性人数&#xff0c;就是条件计数的典型代表。条件计数需要用到COUNTIF函数&#xff0c;函数结构为COUNTIF(统计区域,条件)&#xff0c;在本例第一个公式COUNTIF(B:B,G2)中&#xf…

《栈~~队列~~优先级队列》

目录 前言&#xff1a; 1.stack 1.stack的介绍 2.stack的使用&#xff1a; 3.stack的模拟实现 4.有关stack的oj笔试题 2.queue 1.队列的介绍 2.队列的使用 3.队列的模拟实现 4.有关队列的oj笔试题 3.priority_queue 1.优先级队列的介绍 2.优先级队列的使用 3.优先级队列的模拟实…

挥别2022,坦迎2023。

第一章&#xff1a;CSDN&#xff0c;我来啦&#xff01;第一节&#xff1a;初遇&#xff01;2022-08-13&#xff0c;我和CSDN相遇啦&#xff01;CSDN&#xff0c;你好呀&#xff01;2022年8月13日&#xff0c;是我与你相遇的日子。这是一个值得纪念的时刻。从此之后&#xff0c…

English Learning - L1-10 时态(下) 2023.1.5 周四

English Learning - L1-10 时态&#xff08;下&#xff09; 2023.1.5 周四8 时态8.3 完成时态核心思想&#xff1a;回首往事&#xff08;一&#xff09;现在完成时核心思想用法延续动作延续时间 “动作一直持续了。。。”延续动&#xff08;无延续时间&#xff09; “做过。。…

AtCoder Beginner Contest 284 A - E

题目地址&#xff1a;AtCoder Beginner Contest 284 - AtCoder 一个不知名大学生&#xff0c;江湖人称菜狗 original author: jacky Li Email : 3435673055qq.com Time of completion&#xff1a;2023.1.8 Last edited: 2023.1.8 目录 题目地址&#xff1a;AtCoder Beginner C…

基于FPGA的UDP 通信(一)

引言手头的FPGA开发板上有一个千兆网口&#xff0c;最近准备做一下以太网通信的内容。本文先介绍基本的理论知识。FPGA芯片型号&#xff1a;xc7a35tfgg484-2网口芯片&#xff08;PHY&#xff09;&#xff1a;RTL8211网络接口&#xff1a;RJ45简述以太网什么以太网&#xff1f;以…

k8s之实战小栗子

写在前面 本文一起看一个基于k8s的实战小栗子&#xff0c;在这篇文章 中我们基于docker搭建了一个WordPress网站。本文就通过k8s再来实现一遍。架构图如下&#xff1a; ![在这里插入图片描述](https://img-blog.csdnimg.cn/9c73ac0c183a429a8f4b1a2feb363527.png 从上图可以…

使用Origin计算数据的上升\下降时间

使用Origin计算上升/下降时间计算上升时间1导入数据&#xff0c;做图2、选择合适的数据范围3、选择上升时间和上升范围两个测量参数&#xff0c;获得结果4、更改区间&#xff0c;并导出数据计算下降时间1、将感兴趣区域移动到下降沿2、更改为测量下降沿参数获得结果上升时间小工…

【Kotlin】空安全 ④ ( 手动空安全管理 | 空合并操作符 ?: | 空合并操作符与 let 函数结合使用 )

文章目录一、空合并操作符 ?:二、空合并操作符与 let 函数结合使用一、空合并操作符 ?: 空合并操作符 ?: 用法 : 表达式 A ?: 表达式 B如果 表达式 A 的值 不为 null , 则 整个表达式的值 就是 表达式 A 的值 ; 如果 表达式 A 的值 为 null , 则 整个表达式的值 就是 表达…

vue-路由的使用方式

1.下载路由 使用npm的下载: # vue2对应版本 npm i vue-router3# vue3对应版本 npm i vue-router42.路由初试化 路由的三种模式: history, 指定路由的模式, 有hash,history,memory三种模式,一般使用第一种和第三种模式 createWebHashHistory hash模式 > http://localhost…

十大字符串函数与内存操作函数

前言&#xff1a;我们知道在C语言的库中有许许多多的库函数&#xff0c;今天我就来介绍一下自己对两大类库函数中一些常用函数的认识和理解&#xff0c;希望对大家有帮助。 说明&#xff1a;下文中会花较大篇幅实现这些库函数的模拟&#xff0c;请大家不要觉得库函数直接用就好…

UNet入门总结

作者&#xff1a;AI浩 来源&#xff1a;投稿 编辑&#xff1a;学姐 Unet已经是非常老的分割模型了&#xff0c;是2015年《U-Net: Convolutional Networks for Biomedical Image Segmentation》提出的模型。 论文连接&#xff1a;https://arxiv.org/abs/1505.04597 在Unet之前…

Android 深入系统完全讲解(4)

4 SystemServer 创建过程 SystemServer 进程非常关键了&#xff0c;我们上层的服务都是在这里以线程的形式存在&#xff0c;比如 AMS&#xff0c;PMS&#xff0c;WindowManagerService&#xff0c;壁纸服务&#xff0c;而关于调试这个服务进程&#xff0c;我们随后就会讲到。 …

虚拟人-面部表情-Audio2Face语音驱动表情

任务&#xff1a; 输入自己的音频&#xff0c;导入maya模型&#xff0c;让maya模型通过音频驱动说话 教程&#xff1a; https://www.bilibili.com/video/BV1rZ4y1R7H4/?p2&spm_id_frompageDriver&vd_sourceef114f70c3fd4d5394f12dbd3d022bbe 一.下载和安装 1.首先…

Java面试常见问题-SE篇

JavaSE面试问题汇总①int和Integer的区别为什么设计封装类型&#xff1f;JDK、JRE、JVM的区别和equals方法的区别hashCode()与equals()之间的关系泛型中extends和super的区别String、StringBuffer、StringBuilder的区别重载和重写的区别接口和抽象类的区别List与Set的区别Array…