当前位置: 首页 > news >正文

C++笔记---异常

1. 异常的概念

1.1 异常和错误

异常通常是指在程序运行中动态出现的非正常情况,这些情况往往是可以预见并可以在不停止程序的情况下动态地进行处理的。

错误通常是指那些会导致程序终止的,无法动态处理的非正常情况。例如,越界访问、栈溢出、语法错误等等。错误往往无法预见,需要程序员进行调试来发现出错的原因。

1.2 异常处理机制

C++提供了一套异常处理机制,用于管理和控制程序中可能出现的异常。这个机制基于三个关键的关键字:throw、try和catch。

  • throw关键字用于抛出一个异常。异常可以是任何类型的对象,但通常是从std::exception派生的类的实例。
  • try块包围可能会抛出异常的代码。如果在try块中发生异常,程序会立即停止执行当前的函数,并开始在包含try块的函数上下文中搜索匹配的catch块。
  • catch块定义了异常处理代码。每个catch块都有一个异常声明,用于指定它能够捕获的异常类型。当try块中抛出一个异常时,程序会尝试匹配catch块中的异常声明,并执行匹配的catch块中的代码。
try
{// 可能抛出异常的代码// ...
}
catch(Exception e) 
{// 处理异常或显示错误信息的代码// ...
}
// 如果需要可继续增加catch块

其中,Exception为可接收异常对象的类型(与异常对象的类型相同,异常对象的父类,异常对象可以发生隐式类型转换的类型)。

与传参的规则相似,能传参给e就能捕获,其中用父类来捕获子类异常在异常继承体系中非常实用,例如:除零异常类继承自算术异常类,那么就可以使用算术异常类来捕获除零异常。 


 2. 异常的抛出与捕获

程序出现问题时,我们通过抛出(throw)一个对象来引发一个异常,该对象的类型以及当前的调用链决定了应该由哪个catch的处理代码来处理该异常。

当异常被抛出后,程序会立即跳转到能捕获该异常的最近的catch块处,也就是说:

(1)当前try块中的代码会立即停止执行,并沿着调用链往回匹配能够捕获该异常的catch块。

(2)在匹配catch块的过程中,当前函数栈帧未能处理掉异常,则函数栈帧会被立即销毁(该函数栈帧中已定义的对象全部进入析构流程)并返回上一层函数调用,继续匹配catch块。

若在返回到main函数之后都未能处理掉异常,那么该异常就成为了一个错误,程序会立即终止并报错。

#include<iostream>
#include<string>
using namespace std;double Divide(int a, int b)
{try{// 当b == 0时抛出异常if (b == 0){string s("Divide by zero condition!");throw s;} else{return ((double)a / (double)b);}} catch(int errid){cout << "Divide:" << errid << endl;} return 0;
} void Func()
{int len, time;cin >> len >> time;try{cout << Divide(len, time) << endl;} catch(const char* errmsg){cout << "Func:" << errmsg << endl;}// 除数为0时,此行不会被执行cout << __FUNCTION__ << ":" << __LINE__ << "行执行" << endl;
} int main()
{while (1){try{Func();} catch(const string& errmsg){cout << "main:" << errmsg << endl;}} return 0;
}

抛出的异常实质上是异常对象的拷贝,因为被抛出的异常对象可能是一个局部对象,函数栈帧被销毁之后该对象也会被销毁(这里的处理类似于函数传值返回)。

但是这里有一个例外,就是在使用左值引用捕获异常时,异常也能被捕获(一般来说异常对象的拷贝应该是右值,无法使用左值引用接收),且此时引用的是原始异常对象,原始异常对象的生命周期也被延长至catch块末尾。

异常对象的拷贝在catch块运行结束之后销毁。


 3. 异常重新抛出

在catch块中再次使用throw语句会将当前catch块捕获到的异常原样抛出

// 下面程序模拟展⽰了聊天时发送消息,发送失败补货异常,但是可能在
// 电梯地下室等场景⼿机信号不好,则需要多次尝试,如果多次尝试都发
// 送不出去,则就需要捕获异常再重新抛出,其次如果不是网络差导致的
// 错误,捕获后也要重新抛出。
void _SeedMsg(const string& s)
{if (rand() % 2 == 0){throw HttpException("网络不稳定,发送失败", 102, "put");} else if (rand() % 7 == 0){throw HttpException("你已经不是对象的好友,发送失败", 103, "put");} else{cout << "发送成功" << endl;}
}void SendMsg(const string& s)
{// 发送消息失败,则再重试3次for (size_t i = 0; i < 4; i++){try{_SeedMsg(s);break;} catch(const Exception & e){// 捕获异常,if中是102号错误,网络不稳定,则重新发送// 捕获异常,else中不是102号错误,则将异常重新抛出if (e.getid() == 102){// 重试三次以后失败了,则说明网络太差了,重新抛出异常if (i == 3)throw;cout << "网络较差,开始第" << i + 1 << "重试" << endl;} else{throw;}}}
} int main()
{srand(time(0));string str;while (cin >> str){try{SendMsg(str);} catch(const Exception & e){cout << e.what() << endl << endl;} catch(...){cout << "Unkown Exception" << endl;}}return 0;
}

4. 异常安全问题

4.1 捕获意外的异常和未知异常

前面说过,异常如果未被捕获就会成为错误,所以我们在main函数中一般会这样来写以避免异常未被捕获的情况:

int main()
{try{Func();} catch(const Exception & e){cout << e.what() << endl << endl;} catch(...){cout << "Unkown Exception" << endl;}return 0;
}

其中,这里的Exception代表异常继承体系中所有异常的父类(自定义了异常继承体系或使用了标准库中的异常继承体系),也就是说其可以接收继承体系中任意类型的异常,从而保证意外抛出的异常也能被捕获。

"..."代表任意类型的被抛出的异常,假如被该块捕获,说明该异常不在异常继承体系中,是未知的异常。

4.2 异常处理导致的内存泄露

异常抛出后,后面的代码就不再执行,前面申请了资源(内存、锁等),后面进行释放,但是中间可能会抛异常就会导致资源没有释放,这里由于异常就引发了资源泄漏,产生安全性的问题。

我们可以采取先捕获异常并将资源释放之后重新抛出的方式处理这种情况,但这样做代码的可维护性较差,后面智能指针章节讲的RAII方式解决这种问题是更好的。

double Divide(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 << Divide(len, time) << endl;} catch(...){// 捕获异常释放内存cout << "delete []" << array << endl;delete[] array;throw; // 异常重新抛出,捕获到什么抛出什么} cout << "delete []" << array << endl;delete[] array;
} int main()
{try{Func();} catch(const char* errmsg){cout << errmsg << endl;} catch(const exception & e){cout << e.what() << endl;} catch(...){cout << "Unkown Exception" << endl;} return 0;
}

其次析构函数中,如果抛出异常也要谨慎处理,比如析构函数要释放10个资源,释放到第5个时抛出异常,则也需要捕获处理,否则后面的5个资源就没释放,也资源泄漏了。《Effctive C++》第8个条款也专门讲了这个问题,别让异常逃离析构函数


5. 异常规范

5.1 异常处理的最佳实践

在使用C++异常处理时,应当遵循一些最佳实践,包括:

  • 只在真正无法通过常规错误处理机制恢复的情况下抛出异常。
  • 尽可能地捕获和处理异常,以提供清晰的错误报告和恢复策略。
  • 不要使用裸的throw语句,总是在try块中使用。
  • 使用noexcept关键字来标记那些不应该抛出异常的函数,这有助于编译器优化性能。

5.2 noexcept关键字

对于用户和编译器而言,预先知道某个程序会不会抛出异常大有裨益,知道某个函数是否会抛出异
常有助于简化调用函数的代码。

C++98中函数参数列表的后面接throw(),表示函数不抛异常,函数参数列表的后面接throw(类型1,类型2...)表示可能会抛出多种类型的异常,可能会抛出的类型用逗号分割。

C++98的方式这种方式过于复杂,实践中并不好用,C++11中进行了简化,函数参数列表后面加noexcept表示不会抛出异常,啥都不加表示可能会抛出异常。

编译器并不会在编译时检查noexcept,也就是说如果一个函数用noexcept修饰了,但是同时又包含了throw语句或者调用的函数可能会抛出异常,编译器还是会顺利编译通过的(有些编译器可能会报个警告)。但是一个声明了noexcept的函数抛出了异常,程序会调用 terminate函数 终止程序。

noexcept(expression)还可以作为一个运算符去检测一个表达式是否有可能会抛出异常,可能会则返回false,不会就返回true。 

// C++98
// 这里表示这个函数只会抛出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
size_type size() const noexcept;
iterator begin() noexcept;
const_iterator begin() const noexcept;double Divide(int a, int b) noexcept
{// 当b == 0时抛出异常if (b == 0){// 假如抛出则会报错throw "Division by zero condition!";} return(double)a / (double)b;
} int main()
{try{int len, time;cin >> len >> time;cout << Divide(len, time) << endl;} catch(const char* errmsg){cout << errmsg << endl;} catch(...){cout << "Unkown Exception" << endl;} int i = 0;cout << noexcept(Divide(1, 2)) << endl;cout << noexcept(Divide(1, 0)) << endl;cout << noexcept(++i) << endl;return 0;
}

6. C++标准库中的异常继承体系

其中std::exception为所有异常类的父类,其包含一个虚函数what,该函数在被调用后返回异常信息。该继承体系中所有的子异常类都重写了该函数,以表示不同的异常信息。

 具体信息参考:exception - C++ Reference

一般公司中都会写一套自己的异常体系,标准库中的异常体系其实用的不多。

相关文章:

C++笔记---异常

1. 异常的概念 1.1 异常和错误 异常通常是指在程序运行中动态出现的非正常情况&#xff0c;这些情况往往是可以预见并可以在不停止程序的情况下动态地进行处理的。 错误通常是指那些会导致程序终止的&#xff0c;无法动态处理的非正常情况。例如&#xff0c;越界访问、栈溢出…...

Python 操作数据库:读取 Clickhouse 数据存入csv文件

import pandas as pd from clickhouse_driver import Client import timeit import logging import threading from threading import Lock from queue import Queue from typing import List, Dict, Set from contextlib import contextmanager import os import time# 配置参…...

如何找到系统中bert-base-uncased默认安装位置

问题&#xff1a; 服务器中无法连接huggingface&#xff0c;故需要自己将模型文件上传 ubuntu 可以按照这个链接下载 Bert下载和使用&#xff08;以bert-base-uncased为例&#xff09; - 会自愈的哈士奇 - 博客园 里面提供了giehub里面的链接 GitHub - google-research/be…...

在启动 Spring Boot 项目时,报找不到 slf4j 的错误

而且 tomcat 的启动信息不知道为什么输出出来了 问 AI 得到的解决方案&#xff1a; 将 pom.xml 中的如下配置替换成这样&#xff0c;排除这个插件 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring - boot - starter - …...

android-12-source-code--write-file-function

find /app4/lineage19_oneplus6/system/ -name "*.cpp" -type f | while read -r k ; do ( grep -i write $k | grep -i file && echo $k ;) ; done获得android::base::WriteStringToFile, 进一步修改 find /app4/lineage19_oneplus6/system/ -name &qu…...

SQL(2)

一.时间盲注 有回显时用Union带出数据&#xff0c;只显示是否时可用布尔盲注得出数据&#xff0c;那如果没有任何输出时&#xff1f; 比如无论查询什么&#xff0c;都显示success&#xff0c;同一个回应&#xff0c;无法直接从服务器注入出任何数据&#xff0c;但是我们可以利…...

【IC每日一题:AMBA总线--APB协议时序及Verilog实现】

AMBA总线--APB协议时序及Verilog实现 1 APB3协议1.1 APB3时序1.1.1 APB写操作1.1.2 APB读操作 2 代码2.1 apb_master2.2 apb_slave 【博客首发于微信公众号《漫谈芯片与编程》&#xff0c;欢迎专注一下&#xff0c;多谢大家】 AMBA总线是用于连接微控制器和外围设备的总线协议&…...

抢先看!为什么很多公司会强行给员工电脑加屏幕水印?千字长文来解答

2024年度热议&#xff1a;为什么很多公司会强行给员工电脑加屏幕水印&#xff1f; 有人说&#xff1a;概是为了让员工时刻铭记&#xff0c;工作就像这水印&#xff0c;无处不在&#xff0c;想逃也逃不掉&#xff01; “玩归玩&#xff0c;闹归闹”。 本文将对此进行详尽解答&…...

【AI技术】PaddleSpeech部署方案

【AI技术】PaddleSpeech部署方案 技术介绍优点缺点 部署基础环境的搭建分步详解国内镜像源切换所需环境1 g所需环境2 vim所需环境3 cuda所需环境4 cudnn所需环境5 ssl源码拉取PaddleSpeech环境安装 部署文件分享DockerHub 技术介绍 PaddleSpeech是飞浆平台的一款TTS框架。 优…...

可灵开始“独闯”,全面拥抱AI的快手能否尝到“甜头”?

现任谷歌CEO桑达尔皮查伊曾说到&#xff0c;“人工智能是我们人类正在从事的最为深刻的研究方向之一&#xff0c;甚至要比火与电还更加深刻。” 正如&#xff0c;Sora诞生时&#xff0c;在官方表述中被称为“世界模拟器”&#xff0c;它理解真实的规则&#xff0c;并在此基础上…...

qt QtConcurrent 详解

1、概述 QtConcurrent是Qt框架中用于简化多线程编程的一个模块&#xff0c;它提供了高层次的API来实现并行计算&#xff0c;而不需要开发者直接管理线程的创建、调度和销毁。QtConcurrent主要通过QFuture和QThreadPool来进行并发任务的执行&#xff0c;能够自动利用系统的所有…...

基于构件的软件开发、软件维护、区块链技术及湖仓一体架构的应用

目录 试题一 论基于构件的软件开发方法及其应用 试题二 论软件维护方法及其应用 试题三 论区块链技术及应用 试题四 论湖仓一体架构及其应用 相关推荐 试题一 论基于构件的软件开发方法及其应用 基于构件的软件开发(Component-Based Software Development&#xff0c;CBSD…...

【在Typora中绘制用户旅程图和甘特图】

在 Typora 中可以使用 Mermaid 绘制用户旅程图&#xff08;User Journey Map&#xff09;&#xff0c;但由于 Mermaid 并不直接支持用户旅程图&#xff0c;我们可以通过一些图表的变通方式&#xff08;比如流程图或甘特图&#xff09;来表示用户旅程图的结构。用户旅程图通常展…...

【Vue3】知识汇总,附详细定义和源码详解,后续出微信小程序项目(2)

快速跳转&#xff1a; 我的个人博客主页&#x1f449;&#xff1a;Reuuse博客 新开专栏&#x1f449;&#xff1a;Vue3专栏 参考文献&#x1f449;&#xff1a;uniapp官网 ❀ 感谢支持&#xff01;☀ 前情提要 &#x1f53a;因为最近学习的vue语言&#xff0c;发现有很多细节…...

uniapp中使用全局样式文件引入的三种方式

如果你想在 uni-app 中全局引入 SCSS 文件&#xff08;例如 global.scss&#xff09;&#xff0c;可以通过以下步骤进行配置&#xff1a; 方法一&#xff1a;在 main.js 中引入 在 main.js 中引入全局样式&#xff1a; 你可以在 src/main.js 文件中直接引入 SCSS 文件&#xff…...

计算机网络易混淆知识点串记

文章目录 计算机网络易混淆知识点串记各层PDU首部: 计算机网络易混淆知识点串记 各层PDU首部: PUD首部长度 (B:字节)首部单位数据链路–帧帧首:14B帧尾部:4B——IPV420~60字节4B [通过4位二进制表示]IPV6固定首部40字节[可拓展]4BTCP20~60字节4BUDP8B字节...

Java代码审计-模板注入漏洞

一、模板引擎 在Java开发当中&#xff0c;为了将前端和后端进行分离&#xff0c;降低项目代码的耦合性&#xff0c;使代码更加易于维护和管理。除去以上的原因&#xff0c;模板引擎还能实现动态和静态数据的分离。 二、主流模板引擎 在Java中&#xff0c;主流的模板引擎有:Fre…...

如何在Linux中使用Cron定时执行SQL任务

文章目录 前言一、方案分析二、使用步骤1.准备脚本2.crontab脚本执行 踩坑 前言 演示数据需要每天更新监控数据&#xff0c;不想手动执行&#xff0c;想到以下解决方案 navicat 创建定时任务java服务定时执行linux crontab 定时执行sql脚本 一、方案分析 我选择了第三个方案…...

数据集划分

1、 sklearn玩具数据集介绍 数据量小&#xff0c;数据在sklearn库的本地&#xff0c;只要安装了sklearn&#xff0c;不用上网就可以获取 2 sklearn现实世界数据集介绍 数据量大&#xff0c;数据只能通过网络获取&#xff08;科学上网&#xff09; 3 sklearn加载玩具数据集 示…...

带你读懂什么是AI Agent智能体

一、智能体的定义与特性 定义&#xff1a;智能体是一个使用大语言模型&#xff08;LLM&#xff09;来决定应用程序控制流的系统。然而&#xff0c;智能体的定义并不唯一&#xff0c;不同人有不同的看法。Langchain的创始人Harrison Chase从技术角度给出了定义&#xff0c;但更…...

conda相比python好处

Conda 作为 Python 的环境和包管理工具&#xff0c;相比原生 Python 生态&#xff08;如 pip 虚拟环境&#xff09;有许多独特优势&#xff0c;尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处&#xff1a; 一、一站式环境管理&#xff1a…...

Python如何给视频添加音频和字幕

在Python中&#xff0c;给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加&#xff0c;包括必要的代码示例和详细解释。 环境准备 在开始之前&#xff0c;需要安装以下Python库&#xff1a;…...

Mac下Android Studio扫描根目录卡死问题记录

环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中&#xff0c;提示一个依赖外部头文件的cpp源文件需要同步&#xff0c;点…...

宇树科技,改名了!

提到国内具身智能和机器人领域的代表企业&#xff0c;那宇树科技&#xff08;Unitree&#xff09;必须名列其榜。 最近&#xff0c;宇树科技的一项新变动消息在业界引发了不少关注和讨论&#xff0c;即&#xff1a; 宇树向其合作伙伴发布了一封公司名称变更函称&#xff0c;因…...

spring Security对RBAC及其ABAC的支持使用

RBAC (基于角色的访问控制) RBAC (Role-Based Access Control) 是 Spring Security 中最常用的权限模型&#xff0c;它将权限分配给角色&#xff0c;再将角色分配给用户。 RBAC 核心实现 1. 数据库设计 users roles permissions ------- ------…...

在Zenodo下载文件 用到googlecolab googledrive

方法&#xff1a;Figshare/Zenodo上的数据/文件下载不下来&#xff1f;尝试利用Google Colab &#xff1a;https://zhuanlan.zhihu.com/p/1898503078782674027 参考&#xff1a; 通过Colab&谷歌云下载Figshare数据&#xff0c;超级实用&#xff01;&#xff01;&#xff0…...

Spring事务传播机制有哪些?

导语&#xff1a; Spring事务传播机制是后端面试中的必考知识点&#xff0c;特别容易出现在“项目细节挖掘”阶段。面试官通过它来判断你是否真正理解事务控制的本质与异常传播机制。本文将从实战与源码角度出发&#xff0c;全面剖析Spring事务传播机制&#xff0c;帮助你答得有…...

Modbus转Ethernet IP深度解析:磨粉设备效率跃升的底层技术密码

在建材矿粉磨系统中&#xff0c;开疆智能Modbus转Ethernet IP网关KJ-EIP-101的应用案例是一个重要的技术革新。这个转换过程涉及到两种主要的通信协议&#xff1a;Modbus和Ethernet IP。Modbus是一种串行通信协议&#xff0c;广泛应用于工业控制系统中。它简单、易于部署和维护…...

旋量理论:刚体运动的几何描述与机器人应用

旋量理论为描述刚体在三维空间中的运动提供了强大而优雅的数学框架。与传统的欧拉角或方向余弦矩阵相比&#xff0c;旋量理论通过螺旋运动的概念统一了旋转和平移&#xff0c;在机器人学、计算机图形学和多体动力学领域具有显著优势。这种描述不仅几何直观&#xff0c;而且计算…...

暴雨新专利解决服务器噪音与性能悖论

6月1日&#xff0c;我国首部数据中心绿色化评价方面国家标准《绿色数据中心评价》正式实施&#xff0c;为我国数据中心的绿色低碳建设提供了明确指引。《评价》首次将噪音控制纳入国家级绿色评价体系&#xff0c;要求从设计隔声结构到运维定期监测实现闭环管控&#xff0c;加速…...