[C++]异常笔记

         我不怕练过一万种腿法的对手,就怕将一种腿法 练一万次的对手。        

什么是C++的异常

        在C++中,异常处理通常使用try-catch块来实现try块用于包含可能会抛出异常的代码,而catch块用于捕获并处理异常。当异常被抛出时,程序会跳过try块中未执行的代码,并在catch块中执行适当的处理操作。如果没有抛出异常,则catch块将被跳过。

       以下是一个简单的C++程序,演示了如何使用异常处理:

#include <iostream>

int main() {
  try { // 尝试执行下面的代码块,如果发生异常,则跳转到catch块处理异常
    int x = 10; 
    int y = 0; 
    if (y == 0) { // 如果y等于0,抛出一个异常
      throw "Division by zero!"; // 抛出一个字符串类型的异常对象,内容为"Division by zero!"
    }
    int result = x / y; 
    std::cout << "Result: " << result << std::endl; 
  }
  catch (const char* error) { // 捕获一个字符串类型的异常对象,将异常对象赋值给变量error
    std::cerr << "Error: " << error << std::endl; // 输出错误消息,内容为"Error: Division by zero!"
  }
  return 0; 
}

/*
在上面的程序中,try块包含了可能会抛出异常的代码,
包括将变量y赋值为0和使用除法运算符计算x除以y的结果。
如果y等于0,程序会抛出一个异常,内容为"Division by zero!"。
然后,catch块用于捕获并处理该异常,输出错误消息"Error: Division by zero!"。
*/

异常对象

 

  •        异常对象是一种特殊的对象。编译器依据异常抛出表达式构造异常对象(即异常对象总是被拷贝)。对象的类型是由表达式所表示对象的静态编译类型决定的。如Parent& rObj = Child; throw rObj;时会抛出Parent类型的异常对象。
  •   异常对象存放在内存特殊位置,该位置既不是栈也不是堆,在Windows中是放在线程信息TIB中。该对象由异常机制负责创建和释放!(g++和vc下存储区域处理略有差异)。
  •   异常对象不同于函数的局部对象,局部对象在函数调用结束后就被自动销毁,而异常对象将驻留在所有可能激活的catch语句都能访问到的内存空间中。当异常对象与catch语句成功匹配后,在该catch语句的结束处被自动析构
  •   在函数中返回局部变量的指针或引用几乎肯定会造成错误。同理,在throw语句中抛出局部变量的指针或引用也几乎是错误的
// 捕获异常对象 (值,引用,指针)
#include <iostream>
#include <string>
using namespace std;

class MyException
{
public:
    MyException() { cout << "MyException():" << this << endl; }
    MyException(const MyException&) { cout << "MyException(const MyException&):" << this << endl; }

    ~MyException() { cout << "~MyException():" << this << endl; }

    void what() { cout << "MyException: this = " << this << endl; }
};

class MyChildExcept : public MyException
{
public:
    MyChildExcept() { cout << "MyChildExcept():" << this << endl; }
    MyChildExcept(const MyChildExcept&) { cout << "MyChildExcept(const MyChildExcept&):" << this << endl; }

    ~MyChildExcept() { cout << "~MyChildExcept():" << this << endl; }

    void what() { cout << "MyChildExcept: this = " << this << endl; }
};

void func_local()
{
    // throw 局部对象
    MyException localEx;
    throw localEx;   //尽管localEx是个局部对象,但这里会将其复制构造出一个异常对象,并存储在TIB中。而不是真正的将局部对象抛出去!
}

void func_temp()
{
    //throw 临时对象
    MyException();       //临时对象1
    throw MyException(); //编译器会将这个临时对象直接存储在线程TIB中,成为异常对象(注意与临时对象1存储位置一般相距较远!)
}

void func_ptr()
{
    //throw 指针
    throw new MyException(); //注意:异常对象是复制的堆对象而来的指针(存在内存泄漏风险!!!)
}

void func_again()
{
    MyChildExcept child;
    MyException& re = child; //注意抛出的是re的静态类型的异常对象,即MyException,而不是MyChildExcept;
    throw re;
}

int main()
{
    cout << "----------------------------------catch by value------------------------------" << endl;
    //按值捕获
    try {
        func_local();        //throw MyExecption()
    }
    catch (MyException e) {  //复制异常对象,须额外进行一次拷贝!
        cout << "catch (MyException e)" << endl;
        e.what();
    }

    cout << "--------------------------------catch by reference----------------------------" << endl;
    //按引用捕获
    try {
        func_temp();
    }
    catch (MyException& e) { //直接引用异常对象,无须拷贝
        cout << "catch (MyException& e)" << endl;
        e.what();
    }

    cout << "---------------------------------catch by pointer-----------------------------" << endl;
    //按指针捕获
    try {
        func_ptr();
    }
    catch (MyException* e) { //按指针捕获(可能造成内存泄漏)
        cout << "catch (MyException* e)" << endl;
        e->what();
        delete e;  //释放堆对象,防止内存泄漏
    }

    cout << "------------------------------------throw again-------------------------------" << endl;
    //二次抛异常
    try {
        try {
            func_again();
        }
        catch (MyException& e) {
            e.what();

            //注意以下两种方式不同
            //1. 在throw后指定异常对象为e
            //throw e; //e会继续复制一份,并抛出复制的异常对象而e本身会被释放!

            //2.throw后不指定任何对象,只要是在catch中捕获的,一律抛出去。
            throw;    //此时,e本身再被抛出去。不会另外构造异常对象。
        }
    }
    catch (MyException& e) {
        e.what();
    }

    return 0;
}


 (参考博客:https://www.cnblogs.com/5iedu/p/11270922.html)

异常的抛出和捕获

异常的抛出和匹配原则

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

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

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

//栈展开的测试

#include <iostream>

// 自定义异常类,继承自std::exception类
class MyException : public std::exception {
public:
  // 重写what()方法以返回异常信息字符串
  const char* what() const noexcept override {
    return "MyException: Something went wrong!";
  }
};

// 函数func3,抛出一个MyException异常
void func3() {
  std::cout << "func3: throwing MyException" << std::endl;
  throw MyException(); // 抛出一个MyException异常
  std::cout << "func3: return" << std::endl;   //如果抛出异常,这里就不会执行

}

// 函数func2,调用函数func3
void func2() {
  std::cout << "func2: calling func3" << std::endl;
  func3(); // 调用函数func3
  std::cout << "func2: return" << std::endl;
}

// 函数func1,调用函数func2
void func1() {
  std::cout << "func1: calling func2" << std::endl;
  func2(); // 调用函数func2
  std::cout << "func1: return" << std::endl;
}


int main() {
  try {
    std::cout << "main: calling func1" << std::endl;
    func1(); // 调用函数func1
  }
  catch (const std::exception& e) { // 捕获一个std::exception类型的异常对象,将异常对象赋值给变量e
    std::cerr << "Exception caught: " << e.what() << std::endl; // 输出错误消息,内容为捕获的异常信息
  }
  return 0; // 程序结束
}

异常的重新抛出

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

namespace skate{
	// 服务器开发中通常使用的异常继承体系
	class Exception{
	public:
		Exception(const string& errmsg, int id)
			:_errmsg(errmsg)
			, _id(id){
		}

		virtual string what() const{
			return _errmsg;
		}

		int getid() const{
			return _id;
		}

	protected:
		string _errmsg;  // 描述错误信息
		int _id;         // 错误编码

		// 堆栈信息
	};

	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 SeedMsg(const string& str){
		if (rand() < (RAND_MAX /4)*3){
			throw HttpServerException("SeedMsg::网络错误", 2, "put");
		}
		else if (rand() < RAND_MAX /3){
			throw HttpServerException("SeedMsg::你已经不是对方好友", 1, "post");
		}
		else{
			cout << "消息发送成功!->" << str << endl;
		}
	}
}

int main()
{
	srand(time(0));
	while (1)
	{
		::Sleep(1000);

		try
		{
			//skate::HttpServer();
			// 发送出现网络错误,要求重试3次
			// 权限错误就直接报错 
			for (size_t i = 0; i < 3; ++i)
			{
				try
				{
					skate::SeedMsg("你好啊!今晚一起看电影怎么样?");
					break;
				}
				catch (const skate::Exception& e)
				{
					if (e.getid() == 2) // 异常编码的价值,针对某个错误进行特殊处理
					{
						cout << "网络错误,重试发消息第" <<i+1<<"次"<< endl;			//特殊处理
						if (2 == i) cout << "=======网络错误===发送失败======" << endl;  //异常直接被捕获  不重新抛出  而是尝试重试
						continue;  //网络错误,尝试重新发送   /
					} 
					else // 其他错误
					{
						//break;
						//发送失败,直接重新抛出
						throw e;	// 异常重新抛出 
					}
				}
				
			}
			
		}
		catch (const skate::Exception& e) // 这里捕获父类对象就可以
		{
			// 多态
			cout << e.what() << endl;  //此处已经捕获不到 网络错误,因为网络错误没有重新抛出,已经被特殊处理了
		}
		catch (const std::exception& e) // 这里捕获父类对象就可以
		{
			// 多态
			cout << e.what() << endl;
		}
		catch (...)
		{
			cout << "Unkown Exception" << endl;
		}
	}

	return 0;
}

《C++Primer》关于重新抛出

关于异常安全

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

补充关于构造函数与try语句块       

 关于异常规范(C++11 noexcept 声明)

C++0x与C++11异常规格声明方式的不同

  •  void func() throw() { ... } // throw()声明该函数不会产生异常(C++0x)
  •     void func() throw(int, double) { ... } //可能产生int或double类型异常(C++0x)
  •  void func() noexcept { ... } // noexcept声明该函数不会产生异常(C++11)
  •  void func() noexcept(常量表达式) { ... } //由表达式决定是否产生异常(C++11)

ps: 这里学习noexcept关键字的关键主要为了看得懂官方文档的声明,具体细节就做过多介绍了

-end

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

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

相关文章

【ABAP】数据类型(二)「预定义数据类型」

&#x1f482;作者简介&#xff1a; THUNDER王&#xff0c;一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学本科在读&#xff0c;同时任汉硕云&#xff08;广东&#xff09;科技有限公司ABAP开发顾问。在学习工作中&#xff0c;我通常使用偏后端的开发语言A…

一文读懂 Mysql MVCC

&#x1f495;&#x1f495; 推荐&#xff1a;体系化学习Java&#xff08;Java面试专题&#xff09; 文章目录 1、什么是 MVCC2、什么是当前读、快照读3、MVCC 具体解决什么问题4、MVCC 的实现原理4.1、4个隐式字段4.2、undo 日志4.3、Read View 5、使用 MVCC 时&#xff0c;需…

【分布式系统与一致性协议】

分布式系统与一致性协议 CAP原理APCPCA总结BASE理论 一致性拜占庭将军问题 分布式系统是一个硬件或软件组件分布在不同的网络计算机上&#xff0c;彼此之间仅仅通过消息传递进行通信和协调的系统。 分布式系统的设计目标一般包含如下&#xff1a; 可用性&#xff1a;可用性是分…

JavaSE-04【方法】

文章目录 JavaSE-04【方法】第一章 方法1.1 方法定义的格式详解1.2 方法定义的三要素1.3 方法调用的流程图解1.4 方法定义的有无参数1.5 方法定义的有无返回值 第二章 方法调用方式以及注意事项2.1 方法调用的注意事项2.2 调用方法的三种形式 JavaSE-04【方法】 第一章 方法 …

【华为OD机试真题2023B卷 JAVAJS】评论转换输出

华为OD2023(B卷)机试题库全覆盖,刷题指南点这里 评论转换输出 时间限制:1s 空间限制:256MB 限定语言:不限 题目描述: 在一个博客网站上,每篇博客都有评论。每一条评论都是一个非空英文字母字符串。 评论具有树状结构,除了根评论外,每个评论都有一个父评论。 当评论保…

再看const成员函数

文章目录 再看函数重载const成员函数保安&#xff08;const&#xff09;能保护所有人&#xff08;类成员&#xff09;吗&#xff1f;mutable修饰类成员 const/非const成员函数的复用 关于函数重载实际上我理解不是很深入&#xff0c;直接导致const成员函数这块出大问题&#xf…

chatgpt赋能python:Python如何升序输出?从入门到实践!

Python如何升序输出&#xff1f;从入门到实践&#xff01; 作为一门高级编程语言&#xff0c;Python是目前应用广泛且最为流行的一门语言之一。它逐渐成为开发者的首选语言&#xff0c;因为它易学易用&#xff0c;可读性强&#xff0c;支持多种编程范式&#xff0c;提供了强大…

【MySQL 数据库】9、存储过程

目录 一、存储过程是什么二、存储过程的基本语法三、MySQL 中的变量(1) 系统变量(2) 用户自定义变量(3) 局部变量 四、if 判断五、参数传递和返回值六、case 语句七、while 循环八、repeat 循环九、loop 循环十、游标十一、条件处理程序 一、存储过程是什么 &#x1f331; 存储…

Java反射与注解

文章目录 一、 注解1.简介2. 元注解3. 自定义注解 二、 反射1. 简介2. 理解Class类并获取Class实例3. 类的加载与初始化4. 类加载器ClassLoader5. 获取运行时类的完整结构6. 动态创建对象执行方法7. 反射操作泛型8. 反射操作注解 一、 注解 1.简介 Annotation是JDK5.0开始引入…

第二章 搭建TS环境

搭建 TypeScript 的开发环境。一个舒适、便捷且顺手的开发环境&#xff0c;不仅能大大提高学习效率&#xff0c;也会对我们日常的开发工作有很大帮助。 这一节我们就来介绍 VS Code 下的 TypeScript 环境搭建&#xff1a;插件以及配置项。对于 TS 文件的执行&#xff0c;我们会…

《横向联邦学习中 PCA差分隐私数据发布算法》论文算法原理笔记

论文地址&#xff1a;https://www.arocmag.com/article/01-2022-01-041.html 论文摘要 为了让不同组织在保护本地敏感数据和降维后发布数据隐私的前提下&#xff0c;联合使用 PCA进行降维和数据发布&#xff0c;提出横向联邦 PCA差分隐私数据发布算法。引入随机种子联合协商方…

linuxOPS基础_linux软件包安装

软件包概述 上图是windows下的软件包 Linux下也有很多可以安装的软件&#xff0c;而这些软件的安装包可细分为两种&#xff0c;分别是源码包和二进制包。 Linux下软件的安装方式 ① RPM软件包安装 > 软件名称.rpm ② YUM包管理工具 > yum install 软件名称 -y ③ 源码…

基于QGIS的长株潭城市群边界范围融合实战

背景 在面向区域的研究过程中&#xff0c;比如一些研究区域&#xff0c;如果是具体的行政区划&#xff0c;比如具体的某省或者某市或者县&#xff0c;可以直接从国家官方的地理数据中直接下载就可以。但如果并没有直接的空间数据那怎么办呢&#xff1f;比如之前遇到的一个场景&…

【郭东白架构课 模块二:创造价值】31 |节点六: 如何组织阶段性的价值交付?

你好&#xff0c;我是郭东白。上节课我们讲了为什么要做阶段性的价值交付&#xff0c;以及进入阶段性价值交付环节的准备工作。有了这些学习基础&#xff0c;这节课我们就可以进行阶段性价值交付了。 在交付的过程中&#xff0c;主要有三部分工作&#xff1a;目标分解、定义交…

数据结构——堆(C语言实现)

文章目录 什么是堆堆的实现堆的结构定义堆的初始化接口堆的销毁接口堆的插入数据接口向上调整建堆接口判断堆是否为空堆的删除数据接口向下调整建堆接口获取堆顶数据获取堆的有效数据个数完整实现代码小结 堆排序堆排序的实现 关于建堆和堆排序时间复杂度的分析向下调整建堆向上…

day52|动态规划13-子序列问题

子序列系列问题 300.最长递增子序列 什么是递增子序列&#xff1a; 元素之间可以不连续&#xff0c;但是需要保证他们所在位置是元素在数组中的原始位置。 dp数组dp[i]表示以nums[i]为结尾的最长递增子序列的长度。递归函数&#xff1a;dp[i] max(dp[j]1,dp[j])初始化条件&…

算法刷题-链表-移除链表元素

链表操作中&#xff0c;可以使用原链表来直接进行删除操作&#xff0c;也可以设置一个虚拟头结点再进行删除操作&#xff0c;接下来看一看哪种方式更方便。 203.移除链表元素 力扣题目链接 题意&#xff1a;删除链表中等于给定值 val 的所有节点。 示例 1&#xff1a; 输入&…

Linux下信号量使用总结

目录 1.Linux下信号量简介 2.POSIX信号量 2.1 无名信号量 2.2 有名信号量 3.System V信号量 1.Linux下信号量简介 信号量是解决进程之间的同步与互斥的IPC机制&#xff0c;互斥与同步关系存在的症结在于临界资源。 临界资源是在同一个时刻只容许有限个&#xff08;一般只有…

【数据结构与算法】03 队列(顺序队列--循环队列--优先级队列--链队列)

一、概念1.1 队列的基本概念1.2 队列的顺序存储结构1.21 顺序队列&#xff08;静态队列&#xff09;1.22 循环队列1.23 优先级队列 1.3 队列的链式存储结构 二、C语言实现2.1 顺序存储2.11 顺序队列2.12 循环队列2.13 优先级队列 2.2 链式存储 一、概念 1.1 队列的基本概念 队…

Linux内核中断和Linux内核定时器

目录 Linux内核中断 Linux内核定时器 Linux内核中断 int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev) 功能&#xff1a;注册中断 参数&#xff1a; irq : 软中断号 gpio的软中断号 软中断号 gpio_to_i…
最新文章