[C++]异常笔记

news2025/1/17 6:00:00

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

什么是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.coloradmin.cn/o/633542.html

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

相关文章

Redis6之简介与安装

目录 一、NoSQL NoSQL 特点 使用场景 二、Redis介绍 简介 特性 使用场景 三、Redis安装 1、下载 2、安装 3、启动、停止 4、补充 四、key键操作 一、NoSQL NoSQL 非关系型数据库&#xff1b;存储原理非常简单(典型的数据类型为k-v),不存在繁杂的关系链&#xff…

LabVIEW与Space Wire配合开发

LabVIEW与Space Wire配合开发 Space Wire是欧洲航天局开发的一种高速、点对点、全双工的串行总线网络&#xff0c;以IEEE1355-1995和LVDS 两个商业标准为基础&#xff0c;汲取了1394技术、ATM技术、以太网技术的优点&#xff0c;同时考虑了空间应用的特点&#xff0c;在故障检…

10 - 守护进程深度分析

---- 整理自狄泰软件唐佐林老师课程 查看所有文章链接&#xff1a;&#xff08;更新中&#xff09;Linux系统编程训练营 - 目录 文章目录 1. 会话与终端的关联1.1 思考1.2 新会话关联控制终端的方法1.3 一些相关推论1.4 一些想法1.5 编程实验&#xff1a;会话与终端 2. 守护进程…

RK android13编译环境搭建与常用编译命令

RK android13编译环境搭建与常用编译命令 1 使用清华的源1.1 备份sources.list1.2 用下面的内容替换/etc/apt/sources.list里面的内容1.3 更新源 2 安装编译环境3 常用编译命令3.1 设置项目的编译命令&#xff0c;环境变量3.1 编译所有模块3.2 编译android3.2 编译kernel3.3 编…

linux驱动学习2-pinctrl子系统和gpio子系统

pinctrl子系统 pinctrl 子系统主要用于管理芯片的引脚。 iomuxc节点介绍 首先我们在/ebf-buster-linux/arch/arm/boot/dts/imx6ull.dtsi 文件中查找 iomuxc 节点&#xff0c;可以看到如下定义 iomuxc: iomuxc20e0000 {compatible "fsl,imx6ul-iomuxc";reg <…

【软件环境安装部署】Linux服务器下 Docker 安装 MongoDB 以及 SpringBoot 整合 MongoDB 开发使用

文章目录 安装测试 MongoDB拉取镜像创建和启动容器登录mongo容器&#xff0c;并进入到【admin】数据库创建一个用户&#xff0c;mongo 默认没有用户连接mongo数据库测试数据库&#xff0c;插入一条语句测试数据库&#xff0c;查询刚才插入的语句查看所有数据库开放指定端口navi…

【状态估计】基于线性卡尔曼滤波器和粒子滤波器无人机估计地形高度(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

python基础----11-----闭包、装饰器、单例、工厂、多线程、socket、正则表达式、递归

一 闭包 闭包&#xff1a;通过封装一个函数&#xff0c; 该函数内部又封装一个函数用于返回&#xff0c;使得得到一个无法直接修改值的临时变量&#xff0c;只能通过函数调用修改。闭包的作用&#xff1a;代替全局变量&#xff0c;避免全局变量的滥用。闭包的优缺点&#xff1…

华为荣耀系列uniapp无法USB连接手机调试问题解决方案汇总

华为荣耀系列是一个异常奇葩的手机&#xff0c;经常出现无法调试的问题。 目前我整理出一套完整的切实多次测试可行的解决方案。 一、打开手机的关于手机 设置里面-一直快速点击版本号&#xff0c;连续点10几下。 此时处于开发者模式。 二、打开开发者选项 1、打开开发者…

搭建一个定制版New Bing吧

项目介绍 项目地址&#xff1a;https://github.com/adams549659584/go-proxy-bingai 引用项目简介&#xff1a;用 Vue3 和 Go 搭建的微软 New Bing 演示站点&#xff0c;拥有一致的 UI 体验&#xff0c;支持 ChatGPT 提示词&#xff0c;国内可用&#xff0c;国内可用&#xff…

功能安全开发学习实践心得-概念篇

【写在前面】 记录功能安全开发学习&实践过程中遇到的坎&#xff0c;此篇为概念&#xff08;即行业/标准术语&#xff09;的梳理。实践过程中发现不清楚概念&#xff0c;交流即没法进行&#xff0c;反之&#xff0c;理清概念的过程&#xff0c;即是把整个开发过程串联的过…

50、基于51单片机NRF24l01无线寻物跟踪儿童防丢器系统(程序+原理图+PCB图+设计资料+参考论文+开题报告+元器件清单等)

摘 要 正现代城市生活节奏越来越快,在城市中生活的人们,由于工作、家庭、个人发展、孩子教育、职场竞争等诸多原因,大脑时刻处于紧张状态,容易产生紧张和焦虑情绪,生活压力也越来越大。长期处于这样的状态中,会导致记忆力下降、注意力不集中、容易丢三落四,比如人们常常会记不…

『DevOpse最佳实践』使用Jenkins和Harbor进行持续集成和交付的解决方案

&#x1f4e3;读完这篇文章里你能收获到 全文采用图文形式讲解学会使用Harbor配置项目学会在Jenkins中配置Harbor推送权限使用Jenkins和Harbor进行持续集成的实践感谢点赞收藏&#xff0c;避免下次找不到~ 文章目录 一、准备工作1. 环境准备2. 修改Docker配置文件3. Docker登陆…

Netty实战(十一)

预置的ChannelHandler和编解码器&#xff08;一&#xff09;HTTP和SSL/TLS的添加和使用 一、SSL和TLS添加二、基于Netty的HTTP程序2.1 HTTP解码器、编码器和编解码器2.2 聚合HTTP消息2.3 HTTP压缩 一、SSL和TLS添加 作为一个通讯框架&#xff0c;通讯数据的安全性也是不可或缺的…

LVS+KeepAlived集群

LVSKeepAlived集群 一.KeepAlived的原理 1.1基于什么协议 KeepAlived基于VRRP热备份协议# VRRP协议号112# VRRP组播地址224.0.0.18# VRRP通告报文的TTL值必须是2551.2如何选举Master 1&#xff09;初始化时根据state判断master和backup。 2&#xff09;最终根据优先级决定m…

【小沐学Python】Python实现在线电子书(Sphinx + readthedocs + github + Markdown)

文章目录 1、简介2、安装3、创建测试工程4、项目文件结构5、编译为本地文件6、编译为http服务7、更改样式主题8、支持markdown9、修改文档显示结构10、项目托管到github11、部署到ReadtheDocs结语 1、简介 Sphinx 是一个 文档生成器 &#xff0c;您也可以把它看成一种工具&…

Win10开启混合现实模拟器

最近要做一个类似工业元宇宙的项目&#xff0c;准备先在Win10上先进行模拟&#xff0c;而Win10已经提供了混合现实模拟器&#xff0c;可以在没有头显的情况下进行模拟。本文讲解如何开启这个模拟器。 微软官方给了一个链接讲述如何开启混合现实模拟器&#xff0c;可以点击这里…

嘀嗒陪诊小程序v1.0.8+小程序前端

嘀嗒陪诊小程序功能相对简单&#xff0c;后台也简捷&#xff0c;如果只是做个陪诊服务的小程序也基本能满足了&#xff0c;整体测试了下海参崴发现BUG&#xff0c;小程序端也能正常为使用&#xff0c;唯一用户授权接口是老的。 应用背景&#xff1a;人口老龄化少子化&#xff…

【数据结构】--单链表力扣面试题⑦环形链表

注&#xff1a;本篇文章不含环形链表的数学推理证明&#xff0c;只提供图解等思路 环形链表是一个非常经典的问题 题述&#xff1a;给定一个链表&#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续追踪 next 指针再次到达&#xff0c;则链表中…

代码随想录算法训练营第五十三天|1143.最长公共子序列|1035.不相交的线|53. 最大子序和

LeetCode1143.最长公共子序列 动态规划五部曲&#xff1a; 1&#xff0c;确定dp数组&#xff08;dp table&#xff09;以及下标的含义&#xff1a;dp[i][j]&#xff1a;长度为[0, i - 1]的字符串text1与长度为[0, j - 1]的字符串text2的最长公共子序列为dp[i][j]。为什么要定…