【C++】:异常

目录

C语言处理错误的方式

C++异常的概念

C++异常的使用

异常的抛出与捕获匹配原则

函数调用链中的栈展开 

异常重新抛出 

异常安全

异常规范

标准库异常体系

自定义异常体系

异常的优缺点


C语言处理错误的方式

  1. 返回值检查:函数返回特定错误码或值标识失败,但需逐层检查且易被忽略。

  2. 全局变量 errno:依赖全局变量记录错误类型,存在线程安全隐患和覆盖风险。

  3. 断言(Assert):通过断言验证逻辑假设,但仅适用于调试且失败直接终止程序。

  4. 非局部跳转(setjmp/longjmp):支持跨函数错误跳转,但易导致资源泄漏和代码混乱。

  5. 信号处理:捕获系统信号处理严重错误,但处理函数功能受限且不可靠。

  6. Goto清理:集中释放资源避免冗余,但滥用会破坏代码结构化逻辑。

C++异常的概念

C++ 异常处理是一种用于管理程序运行时错误的机制,它通过分离错误处理代码和正常逻辑来提高代码的可维护性。 

  • try:包裹可能抛出异常的代码块

  • throw:抛出异常对象(任意类型)

  • catch:捕获并处理特定类型的异常

try {// 可能抛出异常的代码if (error) throw MyException("Error occurred");
} 
catch (const MyException& e) {// 处理 MyException 类型异常std::cerr << e.what() << std::endl;
}
catch (...) {  // 捕获所有异常std::cerr << "Unknown error" << std::endl;
}

C++异常的使用

异常的抛出与捕获匹配原则

1、类型精确匹配

  • 异常捕获基于 类型匹配catch 块按顺序尝试匹配异常类型

  • 被选中的处理代码(catch块)是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。
  • 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个catch的处理代码,如果抛出的异常对象没有捕获,或是没有匹配类型的捕获,那么程序会终止报错。
try {throw 42;  // 抛出 int 类型异常
}
catch (double d) { /* 不会捕获 */ }
catch (int i) {   // 匹配成功std::cout << "Caught int: " << i;
}

2、继承体系中的匹配

  • 基类 catch 块可以捕获派生类异常(需通过 引用或指针 捕获避免对象切片)

  • 捕获和抛出的异常类型并不一定要完全匹配,可以抛出派生类对象,使用基类进行捕获。
  • 推荐实践:优先捕获派生类异常,再捕获基类

try {throw std::runtime_error("Error");
}
catch (const std::runtime_error& e) {  // 优先匹配具体类型std::cerr << "Runtime error: " << e.what();
}
catch (const std::exception& e) {      // 基类捕获兜底std::cerr << "Standard exception: " << e.what();
}

3、特殊匹配规则

  • catch (...) 捕获所有异常(通常用于资源清理),但捕获后无法知道异常错误是什么。

  • const 修饰不影响匹配:catch (std::exception) 与 catch (const std::exception) 视为相同

函数调用链中的栈展开 

  • 当异常被抛出后,首先检查 throw 本身是否在try块内部,如果在则查找匹配的catch语句,如果有匹配的,则跳到catch的地方进行处理。
  • 如果当前函数栈没有匹配的 catch 则退出当前函数栈,继续在上一个调用函数栈中进行查找匹配的catch。找到匹配的catch子句并处理以后,会沿着 catch 子句后面继续执行,而不会跳回到原来抛异常的地方。
  • 如果到达main函数的栈,依旧没有找到匹配的catch,则终止程序。
void func3() 
{std::vector<int> localObj(100);  // RAII 对象throw std::runtime_error("Boom"); // 抛出异常// localObj 自动析构
}void func2() { func3(); }  // 异常继续传播
void func1() { func2(); }  // 异常继续传播int main() 
{try {func1();}catch (const std::exception& e) {std::cerr << "Caught: " << e.what();}return 0;
}
  1. 函数调用链

    • main() 调用 func1()

    • func1() 调用 func2()

    • func2() 调用 func3()

  2. 异常抛出(func3)

    • 在 func3() 中,首先构造局部对象 std::vector<int> localObj(100)(RAII管理内存)。

    • 执行 throw std::runtime_error("Boom"),抛出异常,函数执行中断。

  3. 栈展开(Stack Unwinding)

    • func3 栈帧销毁localObj 的析构函数自动调用,释放分配的100个int内存(RAII确保资源释放)。

    • func2 栈帧销毁:因无局部对象,直接退出。

    • func1 栈帧销毁:同理,无资源需清理。

  4. 异常捕获(main)

    • 异常传播至 main() 的 try 块。

    • catch (const std::exception& e) 捕获异常(std::runtime_error 是 std::exception 的派生类)。

    • 输出错误信息:Caught: Boom

异常重新抛出 

在 catch 块中使用 throw; 重新抛出当前异常

典型场景

  • 记录日志后继续传播异常

  • 部分处理异常后交由上层处理

异常安全

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

异常规范

1、优先使用 noexcept

  • 适用场景

    • 移动操作、析构函数、内存释放函数(如 operator delete)。

    • 明确无失败可能的函数(如数学计算)。

double sqrt(double x) noexcept 
{ // 假设输入已校验,不会抛异常return std::sqrt(x); 
}

2. 避免使用动态异常声明

void oldFunc() throw(std::runtime_error); // C++17 已移除,禁止使用
// 替代方案:通过文档说明可能抛出的异常类型。

3、注意事项 

  1. 在函数的后面接throw(type1, type2, ...),列出这个函数可能抛掷的所有异常类型。
  2. 在函数的后面接throw()noexcept(C++11),表示该函数不抛异常。
  3. 若无异常接口声明,则此函数可以抛掷任何类型的异常。(异常接口声明不是强制的)
// 表示func函数可能会抛出A/B/C/D类型的异常
void func() 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++ 标准库提供了一套层次化的异常类体系,所有标准异常均继承自 std::exception 基类。这些异常类型覆盖了常见的程序错误场景,开发者可以直接使用或继承它们实现自定义异常。

std::exception
├── std::bad_alloc                // 内存分配失败(new 失败)
├── std::bad_cast                 // dynamic_cast 转换失败(非多态类型)
├── std::bad_typeid               // typeid 操作符作用于空指针
├── std::ios_base::failure        // I/O 流错误(如文件打开失败)
|
├── std::logic_error              // 程序逻辑错误(可预防的)
│   ├── std::invalid_argument     // 无效参数(如参数不符合预期范围)
│   ├── std::domain_error         // 数学运算定义域错误(如对负数取对数)
│   ├── std::length_error         // 超出允许长度(如 vector::reserve 超过 max_size)
│   └── std::out_of_range         // 访问越界(如 vector::at 越界索引)
|
└── std::runtime_error            // 运行时错误(不可预见的)├── std::range_error          // 计算结果超出有效范围(如浮点数转换溢出)├── std::overflow_error       // 算术上溢错误├── std::underflow_error      // 算术下溢错误└── std::system_error         // 系统调用错误(含错误码,C++11 引入)
  • exception类的 what成员函数 和 析构函数都定义成了虚函数,方便子类对其进行重写,从而达到多态的效果。
  • 我们也可以去继承exception类来实现自己的异常类,但实际中很多公司都会自己定义一套异常继承体系。

 自定义异常:通过继承 std::runtime_error 或 std::logic_error 添加额外信息。

#include <stdexcept>
#include <string>class NetworkException : public std::runtime_error 
{int error_code_;
public:NetworkException(int code, const std::string& message): std::runtime_error(message), error_code_(code) {}int getErrorCode() const noexcept { return error_code_; }
};// 使用
throw NetworkException(404, "Service not found");

代码示例

#include <iostream>
#include <fstream>
#include <stdexcept>
#include <string>void readConfigFile(const std::string& filename) 
{std::ifstream file(filename);if (!file) {throw std::runtime_error("无法打开文件: " + filename);}std::string line;while (std::getline(file, line)) {if (line.empty()) {throw std::invalid_argument("配置文件存在空行");}// 解析配置...}
}int main() 
{try {readConfigFile("settings.conf");}catch (const std::invalid_argument& e) {std::cerr << "参数错误: " << e.what() << std::endl;}catch (const std::runtime_error& e) {std::cerr << "运行时错误: " << e.what() << std::endl;}catch (const std::exception& e) {std::cerr << "标准异常: " << e.what() << std::endl;}return 0;
}

自定义异常体系

实际中很多公司都会自定义自己的异常体系进行规范的异常管理。

  • 公司中的项目一般会进行模块划分,让不同的程序员或小组完成不同的模块,如果不对抛异常这件事进行规范,那么负责最外层捕获异常的程序员就非常难受了,因为他需要捕获大家抛出的各种类型的异常对象。
  • 因此实际中都会定义一套继承的规范体系,先定义一个最基础的异常类,所有人抛出的异常对象都必须是继承于该异常类的派生类对象,因为异常语法规定可以用基类捕获抛出的派生类对象,因此最外层就只需捕获基类就行了。

一、为何需要自定义异常?

  1. 错误分类:为特定领域(如文件I/O、网络、数据库)定义明确的错误类型。

  2. 携带额外信息:在异常对象中封装错误码、文件名、操作步骤等上下文信息。

  3. 统一接口:继承自 std::exception,兼容标准异常处理逻辑。

二、设计原则

  1. 继承标准异常:所有自定义异常应直接或间接继承 std::exception

  2. 层次化结构:按错误类型分层(如 NetworkException 派生出 TimeoutException)。

  3. 支持多态:通过虚函数(如 what())提供统一的错误信息接口。

  4. 异常安全:确保自定义异常类的构造函数和成员函数不抛出异常。

三、实现步骤

1. 基类设计(兼容标准异常) 

#include <exception>
#include <string>class Exception
{
public:// 构造函数(允许传入错误描述)Exception(int errid, const char* errmsg):_errid(errid), _errmsg(errmsg){}int GetErrid() const{return _errid;}// 重写 what(),返回错误信息virtual string what() const{return _errmsg;}
protected:int _errid;     //错误编号string _errmsg; //错误描述//...
};

2. 派生具体异常类

// 文件操作异常
class FileIOException : public Exception 
{
public:explicit FileIOException(const std::string& filename, const std::string& action): Exception("File Error: Failed to " + action + " file '" + filename + "'") {}
};// 网络超时异常
class NetworkTimeoutException : public Exception 
{
public:NetworkTimeoutException(const std::string& url, int timeout_sec): Exception("Network Timeout: Request to '" + url + "' timed out after " + std::to_string(timeout_sec) + " seconds") {}
};

 3使用自定义异常

void readFile(const std::string& filename) 
{std::ifstream file(filename);if (!file.is_open()) {// 抛出异常throw FileIOException(filename, "open");}// 文件操作...
}try 
{readFile("config.yaml");
} 
catch (const FileIOException& e) 
{std::cerr << "文件操作失败: " << e.what() << std::endl;// 尝试恢复或重试
}
catch (const MyBaseException& e) 
{std::cerr << "通用错误: " << e.what() << std::endl;
}

异常的优缺点

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

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

相关文章

SZU软件工程大学生涯 2022~2026

用于个人面试前自我介绍&#xff0c;防止忘记或谈吐不流利。 面试官您好&#xff0c;我是来自深圳大学计算机与软件学院的软件工程专业的王雅贤。在校期间&#xff0c;我修读了程序设计基础、面向对象程序设计、数据结构、算法分析与设计、操作系统等核心课程&#xff0c;系统…

【JavaWeb学习Day27】

Tlias前端 员工管理 条件分页查询&#xff1a; 页面布局 搜索栏&#xff1a; <!-- 搜索栏 --><div class"container"><el-form :inline"true" :model"searchEmp" class"demo-form-inline"><el-form-item label…

Linux 系统运行 Android 应用的几种方案

这几年&#xff0c;国产操作系统替代正在有条不紊地进行中。但生态是绕不过去的一道坎&#xff0c;指望应用厂商一下子完成国产系统适配也不现实。之前介绍过使用 Wine 运行 Windows 应用的方案&#xff0c;减少了国产系统应用偏少的难题。比如我在办公室使用最多的企业微信&am…

Python进阶教程丨lambda函数

1. lambda函数是什么&#xff1f; 在 Python 里&#xff0c;lambda 函数是一种特殊类型的函数&#xff0c;也被叫做匿名函数。匿名”意味着它不需要像常规函数那样使用 def 来进行命名。lambda lambda 函数本质上是简洁的临时函数 &#xff0c;它适用于只需要简单逻辑的场景&a…

TK矩阵系统:高效管理与智能化操作平台

随着TikTok等社交媒体平台的快速发展&#xff0c;短视频创作和内容运营逐渐成为互联网行业的重要组成部分。为了帮助内容创作者、品牌运营商以及数据分析人员更高效地管理多个TikTok账号并优化运营策略&#xff0c;TK矩阵系统提供了一种全新的解决方案&#xff0c;结合了先进的…

Spring Boot整合Apache BookKeeper教程

精心整理了最新的面试资料和简历模板&#xff0c;有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 Spring Boot整合Apache BookKeeper教程 1. 简介 Apache BookKeeper 是一个高性能、持久化的分布式日志存储系统&#xff0c;适用于需要强一致性和高吞吐量的…

苹果HFS+56TB存储MOV文件出错的恢复方法

HFS文件系统是Apple电脑中默认的最常见的文件系统。HFS来源于UNIX&#xff0c;优势就是稳定性&#xff0c;另外HFS是支持日志功能的&#xff0c;所以很多存储设备也采用了HFS文件系统。再稳定的文件系统也有“马失前蹄”的时候&#xff0c;下面就来聊下HFS出现文件出错、丢失时…

电源电路篇

电源电路篇 一、LDO-Low Dropout Regulator(低压差线性稳压器)1.1 AMS1117-3.3V芯片 二、DCDC-Direct Current to Direct Current(开关稳压器)2.1 降压(Buck)电路2.1.1 TPS5450-5V芯片 一、LDO-Low Dropout Regulator(低压差线性稳压器) LDO是一种线性稳压器&#xff0c;用于提…

java项目之在线购物系统(源码+文档)

项目简介 在线购物系统实现了以下功能&#xff1a; 使用在线购物系统的用户分管理员和用户两个角色的权限子模块。 管理员所能使用的功能主要有&#xff1a;主页、个人中心、用户管理、商品分类管理、商品信息管理、系统管理、订单管理等。 用户可以实现主页、个人中心、我的…

go语言中空结构体

空结构体(struct{}) 普通理解 在结构体中&#xff0c;可以包裹一系列与对象相关的属性&#xff0c;但若该对象没有属性呢&#xff1f;那它就是一个空结构体。 空结构体&#xff0c;和正常的结构体一样&#xff0c;可以接收方法函数。 type Lamp struct{}func (l Lamp) On()…

Unity实现连连看连线效果

1.一个比较简单的向量计算&#xff0c;用的LineRenderer实现&#xff1b; 已知起始A点和终点C点&#xff0c;求B点&#xff1b; 先计算A点到C点的向量取归一化当做方向&#xff0c;再给定一个“模长”&#xff08;B点到A点的模长&#xff09;乘以该方向&#xff0c;最后加上L…

【MySQL】触发器与存储引擎

目录 触发器基本概念触发器操作创建触发器NEW 与 OLD查看触发器删除触发器 注意事项 存储引擎基本概念基本操作查询当前数据库支持的存储引擎查看当前的默认存储引擎查看某个表用的存储引擎创建表时指定存储引擎修改表的存储引擎 触发器 基本概念 概述&#xff1a; 触发器&a…

能“嘎嘎提升”提升用户居住体验的智能家居物联网框架推荐!

智能家居在日常生活中给我们的带来了更多的便利&#xff0c;更让有些用户切实地体会到了科技的魅力&#xff0c;对于想要打造属于自己的智能家居氛围感的用户们&#xff0c;以下是一些能够帮助提升居住体验的智能家居物联网框架及应用&#xff1a; 1. 涂鸦智能&#xff08;Tuy…

DevEco Studio的使用

目录 1.创建ArkTS工程 2.ArkTS工程目录结构&#xff08;Stage模型&#xff09; 构建第一个页面 构建第二个页面 实现页面间的跳转 1.创建ArkTS工程 若首次打开DevEco Studio&#xff0c;请点击Create Project创建工程。如果已经打开了一个工程&#xff0c;请在菜单栏选择…

性能监控——vmstat

性能监控——vmstat ​ 性能监控是对 IT 系统运行效率和有效性的系统观察和测量。它涉及收集、分析和报告各种组件&#xff08;包括应用程序、网络、服务器和数据库&#xff09;的关键性能指标 (KPI)。此过程使用专门的工具来跟踪响应时间、吞吐量、资源利用率和错误率等指标。…

搭建Python量化开发环境:从零开始的完整指南

搭建Python量化开发环境&#xff1a;从零开始的完整指南 在量化投资领域&#xff0c;一个稳定且高效的开发环境是成功的关键。本文将引导你一步步搭建起自己的Python量化开发环境&#xff0c;确保你能够顺利开始编写和运行量化策略。 &#x1f680;量化软件开通 &#x1f68…

图像分割的mask有空洞怎么修补

分享一个对实例分割mask修补的方法&#xff0c;希望对大家有所帮助。 1. 这是我准备分割的图片 2 分割结果 可以看到衣服部分有一些没分割出来&#xff0c;二值化图片能清晰看到衣服部分有些黑色未分出的地方。 3 补全mask区域 import cv2 import numpy as npdef fill_mask_h…

Qt 控件概述 QLabel

目录 QLabel显示类控件 label如何做到与窗口同步变化 边框 Frame QLabel显示类控件 ​​ ​​ textFormat &#xff1a;设置文件格式 ​ Pixmap &#xff1a;标签图片 label如何做到与窗口同步变化 Qt中对应用户的操作 &#xff1a; 事件和信号 拖拽窗口大小就会触发…

词频统计 ccf-csp 2024-2-1

在学习了文本处理后&#xff0c;小 P 对英语书中的 n篇文章进行了初步整理。 具体来说&#xff0c;小 P将所有的英文单词都转化为了整数编号。 假设这 n 篇文章中共出现了 m个不同的单词&#xff0c;则把它们从 1到 m进行编号。 这样&#xff0c;每篇文章就简化为了一个整数…

用爬虫解锁 Shopee 店铺商品数据,开启电商新洞察

在电商竞争白热化的当下&#xff0c;Shopee 作为全球知名的电商平台&#xff0c;汇聚了海量的商品与商机。对于电商从业者、数据分析师、创业者来说&#xff0c;精准掌握 Shopee 店铺的商品信息&#xff0c;就如同手握一把开启财富大门的钥匙。而爬虫技术&#xff0c;无疑是帮助…