[学习]浅谈C++异常处理(代码示例)

浅谈C++异常处理

文章目录

    • 浅谈C++异常处理
      • 一、异常处理基础
        • 1.异常的概念与作用
        • 2.C++异常处理机制(`try`、`catch`、`throw`)
        • 3.基本语法示例
      • 二、标准异常类
        • 1.常见标准异常类:
        • 2.自定义异常类的实现
      • 三、异常安全与最佳实践
        • 1. RAII(资源获取即初始化)与异常安全
        • 2. `noexcept` 关键字的作用与使用场景
        • 3. 避免异常滥用与性能影响
      • 四、高级异常处理技术
        • 1. 嵌套异常(`std::nested_exception`)
        • 2. 异常传播与重新抛出(`throw;`)
        • 3. 异常处理在多线程环境中的注意事项
      • 五、实际应用案例
        • 1. 文件操作中的异常处理
        • 2. 网络编程中的异常管理
        • 3. 大型项目中的异常策略设计


一、异常处理基础

1.异常的概念与作用

异常是程序在运行时发生的意外或错误情况(如除零错误、内存不足、文件不存在等),它打破了正常的程序执行流程。异常处理机制允许开发者:

  1. 分离错误处理代码:将正常逻辑与错误处理解耦,提升代码可读性
  2. 跨函数传递错误:异常可沿调用栈向上传递,无需每层函数显式检查错误
  3. 标准化错误报告:通过异常类型和消息规范错误信息
2.C++异常处理机制(trycatchthrow
  1. throw:当检测到错误时,用throw抛出异常对象(如内置类型、自定义类或标准库异常)
    • 示例:throw 42;(抛出整型)或 throw std::out_of_range("Index invalid");
  2. try:包裹可能引发异常的代码块
  3. catch:捕获并处理特定类型的异常,支持多级捕获(类似switch-case的匹配机制)
3.基本语法示例
#include <iostream>
#include <stdexcept> // 包含标准异常类double divide(int a, int b) {if (b == 0) {throw std::runtime_error("Division by zero"); // 抛出运行时错误}return static_cast<double>(a) / b;
}int main() {try {std::cout << divide(10, 2) << std::endl;  // 正常执行std::cout << divide(5, 0) << std::endl;   // 触发异常} catch (const std::runtime_error& e) {       // 捕获特定异常std::cerr << "[ERROR] " << e.what() << std::endl;} catch (...) {                               // 捕获所有未处理的异常std::cerr << "Unknown exception occurred" << std::endl;}return 0;
}

输出示例

5
[ERROR] Division by zero

应用场景

  1. 文件操作失败处理

    • 当使用std::ifstream打开或读取文件时,若文件不存在、权限不足或格式错误,会抛出std::ifstream::failure异常。例如:
      try {std::ifstream file("data.txt");if (!file) throw std::ifstream::failure("Failed to open file");// 文件读取操作...
      } catch (const std::ifstream::failure& e) {std::cerr << "File error: " << e.what() << std::endl;
      }
      
    • 典型场景:配置文件加载、日志文件读取或数据导入时验证文件有效性。
  2. 动态内存分配失败处理

    • 使用new分配大量内存时,若系统内存不足会抛出std::bad_alloc。例如:
      try {int* large_array = new int[1000000000]; // 可能分配失败
      } catch (const std::bad_alloc& e) {std::cerr << "Memory allocation failed: " << e.what() << std::endl;
      }
      
    • 优化建议:在嵌入式系统或高性能计算中,可预先检查可用内存或使用std::nothrow替代方案。
  3. 自定义异常处理业务逻辑

    • 定义继承自std::exception的异常类(如InvalidUserInputException),用于验证用户输入。例如:
      class InvalidUserInputException : public std::exception {
      public:const char* what() const noexcept override {return "User input contains invalid characters";}
      };void validateInput(const std::string& input) {if (input.find('@') == std::string::npos) {throw InvalidUserInputException();}
      }
      
    • 扩展场景:电商系统中校验订单金额非负,或游戏开发中检查玩家非法操作。

最佳实践

  • 优先捕获具体异常类型(如std::bad_alloc而非通用std::exception)。
  • 在异常处理中记录上下文信息(如文件名、输入值)以便调试。
  • 资源管理类(如数据库连接)应在析构函数中释放资源,而非依赖异常处理。

二、标准异常类

C++标准库提供了丰富的异常类体系,主要通过std::exception基类及其派生类来实现异常处理机制。这些异常类通常包含在<exception>头文件中,为开发者提供了标准化的异常处理方式。

1.常见标准异常类:
  1. std::exception
    所有标准异常类的基类,提供what()虚函数用于获取异常描述信息。

  2. std::runtime_error

    • 用于表示程序运行时发生的错误(如文件不存在、网络连接失败等)
    • 典型派生类:
      • std::overflow_error - 算术运算溢出
      • std::underflow_error - 算术运算下溢
      • std::range_error - 超出有效范围
  3. std::logic_error

    • 用于表示程序逻辑错误(如无效参数、违反前置条件等)
    • 典型派生类:
      • std::invalid_argument - 无效参数
      • std::out_of_range - 超出允许范围
      • std::length_error - 超出最大允许长度
2.自定义异常类的实现

在实际开发中,我们经常需要创建特定于应用的异常类。通过继承std::exception或其派生类,可以保持与标准异常处理机制的一致性。

#include <exception>
#include <string>class MyCustomException : public std::exception {
private:std::string error_msg;  // 存储详细的错误信息public:// 构造函数,允许传入自定义错误信息explicit MyCustomException(const std::string& msg) : error_msg(msg) {}// 重写what()方法,返回错误描述const char* what() const noexcept override {return error_msg.c_str();}// 示例用法:// throw MyCustomException("File processing failed: invalid format");
};

自定义异常类的典型应用场景:

  1. 文件处理异常(如文件格式不符、权限不足)
  2. 网络通信异常(如连接超时、协议错误)
  3. 业务逻辑异常(如交易金额超限、用户状态异常)

实现建议:

  1. 继承自std::exception或其标准派生类
  2. 提供详细的错误信息存储机制
  3. 确保what()方法不会抛出异常(使用noexcept
  4. 考虑添加额外的错误代码或上下文信息字段

三、异常安全与最佳实践

1. RAII(资源获取即初始化)与异常安全

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期管理资源的核心编程范式。通过将资源的获取与对象的构造绑定,资源的释放与对象的析构绑定,确保即使在异常发生时资源也能被正确释放,避免内存泄漏或资源泄露。

典型应用场景

  • 文件操作:使用 std::ifstreamstd::ofstream 封装文件句柄,析构时自动关闭文件。
  • 动态内存管理:通过 std::unique_ptrstd::shared_ptr 管理堆内存,无需手动 delete
  • 锁管理:利用 std::lock_guard 在作用域内自动加锁和解锁,避免死锁。

示例代码

void processFile(const std::string& filename) {std::ifstream file(filename); // 构造函数打开文件if (!file.is_open()) {throw std::runtime_error("Failed to open file");}// 文件操作...(若此处抛出异常,file 析构时会自动关闭文件)
} // 作用域结束,file 析构自动关闭文件
2. noexcept 关键字的作用与使用场景

noexcept 是 C++11 引入的关键字,用于显式声明函数不会抛出异常。合理使用可以优化性能,并为编译器提供更多优化机会。

主要作用

  • 性能优化:标记为 noexcept 的函数可能触发移动语义而非拷贝(如 std::vector 的重新分配)。
  • 契约声明:明确告知调用者该函数不会抛出异常,简化错误处理逻辑。

适用场景

  • 析构函数、移动构造函数、移动赋值运算符等必须声明为 noexcept(标准库容器依赖此保证)。
  • 简单工具函数(如数学计算)、资源释放逻辑等无异常风险的函数。

示例

class Buffer {
public:Buffer(Buffer&& other) noexcept // 移动构造函数不抛出异常: data_(other.data_), size_(other.size_) {other.data_ = nullptr;}~Buffer() noexcept { delete[] data_; } // 析构函数通常不抛出异常
private:int* data_;size_t size_;
};
3. 避免异常滥用与性能影响

异常机制虽然强大,但滥用可能导致性能下降或代码可维护性降低。需注意以下原则:

最佳实践

  • 避免频繁抛出异常:异常处理成本较高,高频场景(如循环内)建议改用错误码或状态检查。
  • 异常 vs 错误码
    • 使用异常处理不可恢复的错误(如内存不足、文件损坏)。
    • 使用错误码处理预期内的错误(如用户输入无效)。
  • 禁用异常的场景:嵌入式系统等对运行时开销敏感的环境,可通过编译选项(如 -fno-exceptions)禁用异常。

性能对比示例

// 低效:在热路径中抛出异常
for (int i = 0; i < 10000; ++i) {try {riskyOperation();} catch (...) { /* 处理 */ }
}// 高效:提前检查避免异常
for (int i = 0; i < 10000; ++i) {if (canPerformOperation()) { // 预先验证safeOperation();}
}

总结:异常安全的核心是资源管理确定性异常使用克制性,结合 RAII 与 noexcept 可显著提升代码健壮性。


四、高级异常处理技术

1. 嵌套异常(std::nested_exception

嵌套异常允许异常对象包含另一个异常,形成异常链,便于捕获和调试多层异常。这在复杂系统中特别有用,例如框架或库的调用栈中可能抛出多种异常的场景。

示例代码

#include <exception>
#include <iostream>
#include <stdexcept>void handle_nested_exception(const std::exception& e) {try {std::rethrow_if_nested(e); // 尝试重新抛出嵌套异常  } catch (const std::exception& nested) {std::cerr << "Nested Exception: " << nested.what() << std::endl;handle_nested_exception(nested); // 递归处理嵌套异常  }
}int main() {try {try {throw std::runtime_error("Primary error");} catch (...) {std::throw_with_nested(std::logic_error("Wrapper error"));}} catch (const std::exception& e) {std::cerr << "Outer Exception: " << e.what() << std::endl;handle_nested_exception(e);}return 0;
}

应用场景

  • 在多层调用的框架中,捕获底层异常并附加高层上下文信息。
  • 日志记录时保留完整的异常链,便于问题溯源。

2. 异常传播与重新抛出(throw;

在异常处理块中,使用 throw; 可以重新抛出当前捕获的异常,保持原始异常类型和调用栈信息。

关键点

  • 仅当在 catch 块内使用时有效,否则行为未定义。
  • 适用于需要部分处理异常但最终仍由上级调用者处理的场景。

示例代码

void process_data() {try {// 可能抛出异常的代码  throw std::runtime_error("Data processing failed");} catch (...) {std::cerr << "Partial handling in process_data()" << std::endl;throw; // 重新抛出  }
}int main() {try {process_data();} catch (const std::runtime_error& e) {std::cerr << "Handled in main(): " << e.what() << std::endl;}return 0;
}

典型场景

  • 中间层函数需要清理资源(如关闭文件),但异常仍需传递给调用者。
  • 实现异常过滤器,选择性重新抛出特定异常类型。

3. 异常处理在多线程环境中的注意事项

多线程中未捕获的异常会导致程序终止(C++11 后通过 std::terminate),需显式处理线程内异常。

解决方案

  1. 使用 try-catch 包裹线程入口函数
    void thread_worker() {try {// 线程逻辑  } catch (const std::exception& e) {std::cerr << "Thread error: " << e.what() << std::endl;}
    }
    
  2. 传递异常到主线程(通过 std::promise 或全局变量):
    std::promise<void> promise;
    std::thread t([&promise] {try {// 线程逻辑  promise.set_value();} catch (...) {promise.set_exception(std::current_exception());}
    });
    try {promise.get_future().get(); // 主线程捕获异常  
    } catch (const std::exception& e) {std::cerr << "Main thread caught: " << e.what() << std::endl;
    }
    t.join();
    

注意事项

  • 避免跨线程直接 throw,因栈展开可能不同步。
  • 使用 std::exception_ptr 存储和跨线程传递异常对象。

五、实际应用案例

1. 文件操作中的异常处理

文件操作是编程中常见的场景,但可能会遇到文件不存在、权限不足或磁盘空间不足等问题。合理的异常处理可以避免程序崩溃,并提供友好的错误提示。

示例代码:读取文件内容并处理异常

def read_file(file_path):try:with open(file_path, 'r', encoding='utf-8') as file:content = file.read()print(f"文件内容读取成功:\n{content}")except FileNotFoundError:print(f"错误:文件 '{file_path}' 不存在,请检查路径。")except PermissionError:print(f"错误:没有权限访问文件 '{file_path}'。")except UnicodeDecodeError:print(f"错误:文件 '{file_path}' 编码格式不支持,请指定正确的编码。")except Exception as e:print(f"未知错误:{str(e)}")# 调用示例
read_file("example.txt")

应用场景

  • 读取配置文件时,避免因文件缺失导致程序中断。
  • 批量处理文件时,记录错误文件路径并跳过,不影响后续文件操作。

2. 网络编程中的异常管理

网络请求可能因连接超时、服务器错误或数据格式问题而失败。异常管理能提高程序的健壮性,尤其是在高并发或分布式系统中。

示例代码:HTTP请求异常处理

import requestsdef fetch_data(url):try:response = requests.get(url, timeout=5)response.raise_for_status()  # 检查HTTP状态码return response.json()except requests.exceptions.Timeout:print(f"请求超时:服务器未在5秒内响应(URL: {url})。")except requests.exceptions.HTTPError as e:print(f"HTTP错误:状态码 {e.response.status_code}(URL: {url})。")except requests.exceptions.JSONDecodeError:print(f"数据格式错误:返回内容非JSON格式(URL: {url})。")except Exception as e:print(f"网络请求失败:{str(e)}")# 调用示例
data = fetch_data("https://api.example.com/data")

应用场景

  • API调用时,处理服务器返回的错误状态码(如404、500)。
  • 爬虫程序中跳过失效链接,避免因单次请求失败终止任务。

3. 大型项目中的异常策略设计

在大型项目中,异常处理需要统一策略,包括日志记录、错误分类和恢复机制。常见的做法包括自定义异常类和全局异常拦截。

示例代码:自定义异常与全局处理

# 自定义异常类
class DatabaseConnectionError(Exception):"""数据库连接失败时抛出"""passclass InvalidInputError(Exception):"""用户输入非法时抛出"""pass# 全局异常处理器
def handle_exceptions(func):def wrapper(*args, **kwargs):try:return func(*args, **kwargs)except DatabaseConnectionError as e:log_error(f"数据库错误:{str(e)}")show_user_message("系统繁忙,请稍后重试。")except InvalidInputError as e:log_error(f"输入错误:{str(e)}")show_user_message("请输入有效数据。")except Exception as e:log_error(f"未捕获的异常:{str(e)}")show_user_message("系统发生未知错误。")return wrapper# 使用装饰器管理异常
@handle_exceptions
def process_user_data(user_input):if not user_input.isdigit():raise InvalidInputError("输入必须为数字")# 模拟数据库操作if not connect_database():raise DatabaseConnectionError("无法连接MySQL")# 辅助函数
def log_error(message):with open("error.log", "a") as f:f.write(f"[ERROR] {message}\n")def show_user_message(message):print(message)

应用场景

  • 微服务架构中,统一处理跨服务调用的超时或数据格式异常。
  • Web框架(如Django、Flask)中通过中间件拦截全局异常,返回标准化错误页面或JSON响应。

关键策略

  1. 分层处理:底层捕获技术异常(如IO错误),业务层捕获逻辑异常(如无效订单)。
  2. 日志分级:错误(Error)记录异常堆栈,警告(Warning)记录可恢复问题。
  3. 用户反馈:对客户端隐藏技术细节,提供明确的操作指引。

研究学习不易,点赞易。
工作生活不易,收藏易,点收藏不迷茫 :)


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

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

相关文章

PHP学习笔记(十)

extends 一个类可以在声明中用extends关键字继承另一个类的方法和属性。PHP不支持多重继承&#xff0c;一个类只能继承一个基类。 被继承的方法和属性可以通过同样的名字重新声明被覆盖&#xff0c;但是如果父类定义或者常量时是使用类final&#xff0c;则不可被覆盖&#xff…

rt-linux里的泛rtmutex锁的调用链整体分析

一、背景 linux系统里有非常多的锁种类&#xff0c;除了spinlock&#xff0c;mutex&#xff0c;rwlock&#xff0c;rwsem&#xff0c;还有rcu及顺序锁&#xff0c;这里面还有不少锁变种&#xff0c;比如spinlock的带bh或者irq字样的lock/unlock&#xff0c;还有nmi里可以用的顺…

LLM多平台统一调用系统-LiteLLM概述

概述 在当今快速发展的AI领域&#xff0c;大语言模型(LLM)已成为技术创新的核心驱动力。然而&#xff0c;随着市场上涌现出越来越多的LLM提供商&#xff08;如OpenAI、Anthropic、Google Gemini、AWS Bedrock等&#xff09;&#xff0c;开发者面临着一个日益复杂的问题&#x…

C#实现MCP Client 与 LLM 连接,抓取网页内容功能!

该专栏优先在飞书发布&#xff0c;欢迎收藏关注&#xff01; https://www.feishu.cn/community/article?id7507084665509904403 前面的课程&#xff0c;我们已经用C#实现了&#xff0c;自己的MCP Client。 下面我们一起来实现&#xff0c;MCP Client与LLM 对接。 一、添加依…

并发编程(6)

指令重排序 指令重排序是指在程序执行过程中&#xff0c;为了提高性能&#xff0c;编译器或处理器会对指令的执行顺序进行重新排列。 指令重排序导致可见性消失 在多线程环境下&#xff0c;每个线程都有自己的工作内存&#xff0c;线程对变量的操作是在工作内存中进行的&…

鸿蒙仓颉开发语言实战教程:页面跳转和传参

前两天分别实现了商城应用的首页和商品详情页面&#xff0c;今天要分享新的内容&#xff0c;就是这两个页面之间的相互跳转和传递参数。 首先我们需要两个页面。如果你的项目中还没有第二个页面&#xff0c;可以右键cangjie文件夹新建仓颉文件&#xff1a; 新建的文件里面没什…

Java 学习笔记:注解、泛型与 IO 流

目录 课程目标 Java 注解(Annotation) 1. 概念与作用 2. 自定义注解示例 3. JDK 内置注解 4.注释 Java 泛型(Generics) 1. 基本语法 2. 通配符与上下限 3. 常见应用场景 Java IO 流 1. 流的分类1.File文件类 2. 字节流与字符流 3. 经典示例:文件拷贝 总结与…

git仓库代码操作

1、从gitee下载代码提交到本地github仓库&#xff0c;保留提交记录 # 查看当前分支 git branch# 查看当前远程仓库 git remote -v# 确保所有更改已提交 git add . git commit -m "准备提交到GitLab"# 添加GitLab远程仓库 git remote add gitlab https://gitlab.com/…

Thinkphp6使用token+Validate验证防止表单重复提交

htm页面加 <input type"hidden" name"__token__" value"{:token()}" /> Validate 官方文档 ThinkPHP官方手册

Mcu_Bsdiff_Upgrade

系统架构 概述 MCU BSDiff 升级系统通过使用二进制差分技术&#xff0c;提供了一种在资源受限的微控制器上进行高效固件更新的机制。系统不传输和存储完整的固件映像&#xff0c;而是只处理固件版本之间的差异&#xff0c;从而显著缩小更新包并降低带宽要求。 该架构遵循一个…

Spring Boot微服务架构(四):微服务的划分原则

微服务划分原则&#xff08;CRM系统案例说明&#xff09; 一、微服务划分的核心原则 单一职责原则&#xff08;SRP&#xff09; 每个微服务只负责一个明确的业务功能服务边界清晰&#xff0c;避免功能混杂便于独立开发、测试和部署 业务领域驱动设计&#xff08;DDD&#xff0…

基于CNN卷积神经网络的带频偏QPSK调制信号检测识别算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 (完整程序运行后无水印) 2.算法运行软件版本 matlab2024b 3.部分核心程序 &#xff08;完整版代码包含详细中文注释和操作步骤视频&#xff09…

从机械应答到深度交互,移远通信如何让机器人“灵魂觉醒”?

你是否还在因机器人的“答非所问”而无奈&#xff0c;为它们的“反应慢半拍”而抓狂&#xff1f;别慌&#xff01;一场引领机器人实现“灵魂觉醒”的技术革命&#xff0c;正如同暗夜中悄然绽放的繁星&#xff0c;彻底颠覆人们对机器人的传统认知。 5月20日&#xff0c;移远通信…

软件的技术架构、应用架构、业务架构、数据架构、部署架构

一、各架构定义 1. 技术架构&#xff08;Technical Architecture&#xff09; 定义&#xff1a;技术架构关注的是支撑系统运行的底层技术基础设施和软件平台&#xff0c;包括硬件、操作系统、中间件、编程语言、框架、数据库管理系统等技术组件的选择和组合方式。它描述了系统…

HTML-前端

目录 开始学习HTML 什么是 HTML? 剖析一个 HTML 元素 嵌套元素 块级元素和内联元素 空元素 属性 为元素添加属性 布尔属性 省略包围属性值的引号 使用单引号还是双引号&#xff1f; 剖析 HTML 文档 HTML 中的空白 实体引用&#xff1a;在 HTML 中包含特殊字符 HT…

多态的总结

什么是多态&#xff1f; 答&#xff1a;多态是多种形态&#xff0c;是为了完成某种行为时&#xff0c;不同对象会产生不同的形态&#xff08;结合车票例子解释&#xff09; 2. 什么是重载、重写(覆盖)、重定义(隐藏)&#xff1f; 答&#xff1a;重载的条件是&#xff1a;在同一…

VBA 读取指定范围内的单元格数据,生成csv文件

目录 一. 需求二. 宏代码三. 添加按钮 一. 需求 ⏹有如下表格&#xff0c;现在想在Excel中添加一个按钮 点击按钮之后&#xff0c;读取该表格中的数据&#xff0c;生成csv文件将csv文件输出到和Excel同级目录 二. 宏代码 Application.PathSeparator&#xff1a;路径分隔符Cr…

【Code Agent Benchmark】论文分享No.15:TAU-Bench

论文名称&#xff1a;τ-bench: A Benchmark for Tool-Agent-User Interaction in Real-World Domains 论文&#xff1a;https://arxiv.org/abs/2406.12045 机构&#xff1a;Sierra Github 链接&#xff1a;https://github.com/sierra-research/tau-bench# 简介 相比于Swe-ben…

Linux下 使用 SSH 完成 Git 绑定 GitHub

文章目录 1、检查 SSH2、生成 SSH key3、添加 SSH key4、验证绑定是否成功 1、检查 SSH Git Bash 中输入ssh命令&#xff0c;查看本机是否安装 SSH&#xff1a; 2、生成 SSH key &#xff08;1&#xff09;输入 ssh-keygen -t rsa 命令&#xff0c;表示我们指定 RSA 算法生…

Java 8 Stream 流操作全解析

文章目录 **一、Stream 流简介****二、Stream 流核心操作****1. 创建 Stream****2. 中间操作&#xff08;Intermediate Operations&#xff09;****filter(Predicate<T>)&#xff1a;过滤数据****1. 简单条件过滤****2. 多条件组合****3. 过滤对象集合****4. 过滤 null 值…