【C++】一篇文章了解C++的异常处理机制

【C++】一篇文章了解C++的异常处理机制

异常

基本异常处理关键字

在 C++ 中,异常处理是一种机制,用于处理程序在运行时发生的异常情况。异常是指程序执行期间发生
的意外事件,比如除以零、访问无效的内存地址等。通过使用异常处理机制,可以使程序更健壮,并能
够处理这些意外情况,避免程序崩溃或产生不可预测的结果。
在 C++ 中,异常处理通常包括以下关键词和概念:

  • try-catch 块: try 块用于标识可能会引发异常的代码块,而 catch 块用于捕获和处理异常。
    catch 块可以针对不同类型的异常进行处理。
  • throw 关键词: throw 用于在程序中显式抛出异常。当发生异常情况时,可以使用 throw 来抛出
    一个特定的异常类型。
  • 异常类型:异常可以是任何类型的数据,但通常是标准库中的异常类或自定义的异常类。标准库提
    供了一些常见的异常类,如 std::exception 及其派生类,用于表示不同类型的异常情况。

核心语法:

关键字作用关键注意点
throw中断当前代码流程,抛出异常对象,跳转到最近的匹配 catch抛出后,throw 之后的代码立即停止执行
try标记 “需要监控异常的代码块”,必须和至少一个 catch 配对仅监控 try 块内的代码,块外的异常不会被捕获
catch按 “类型从上到下” 匹配异常,处理捕获到的异常1. 建议用 const 引用(避免拷贝 + 防止切片);2. catch(...) 捕获所有异常

标准异常体系

C++ 内置了一套标准异常类(都继承自 std::exception),无需自定义就能满足大部分场景:

异常类用途头文件
invalid_argument无效参数(如除数为 0)<stdexcept>
out_of_range越界访问(如 vector::at()<stdexcept>
runtime_error通用运行时错误<stdexcept>
bad_alloc内存分配失败(new 失败)<new>
#include<iostream>#include<stdexcept>usingnamespace std;intdivide(int x,int y){if(y ==0)throwruntime_error("Division by zero error");elsereturn x / y;}intmain(){int x,y; cin >> x >> y;try{int result =divide(x,y); cout <<"Result: "<< result << endl;}catch(const runtime_error& e){ cout <<"Caught an exception: "<< e.what()<< endl;}return0;}

异常的传播

如果函数抛出异常但自身没有 catch,异常会 “沿着调用栈向上传播”,直到找到匹配的 catch

#include<iostream>#include<stdexcept>usingnamespace std;voidfunc3(){throwruntime_error("func3 抛异常");// 源头}voidfunc2(){func3();// 无 catch,异常继续传播}voidfunc1(){func2();// 无 catch,异常继续传播}intmain(){try{func1();}catch(const exception& e){ cout <<"main 捕获异常:"<< e.what()<< endl;// 最终在这里捕获}return0;}

异常安全

异常的核心问题是 “打断代码执行流程”,如果代码没做好防护,会导致两类严重问题:资源泄漏对象状态损坏

从上面的问题可以引出「异常安全」的核心定义:程序抛出异常后,依然保证 “资源不泄漏、对象状态有效(不损坏)、操作可预期” 的特性

C++ 把异常安全分为 4 个级别(从弱到强),重点掌握前 3 个:

安全级别核心定义对应解决的问题示例场景
1. 不抛保证(No-throw)函数绝不抛出任何异常(用 noexcept 标记),始终执行成功。避免函数抛异常引发的所有问题数学运算、析构函数、swap
2. 基本保证(Basic)异常抛出后,资源不泄漏、对象状态有效(但可能未完成预期操作)。解决资源泄漏 + 对象不损坏vector::push_back
3. 强保证(Strong)异常抛出后,对象状态回滚到异常前(操作要么全成,要么全败)。解决 “半修改” 的状态损坏问题业务核心数据修改
4. 不抛销毁(No-throw destruction)析构函数 / 资源释放函数绝不抛异常。避免栈展开时程序崩溃
noexcept关键字

noexcept是一个异常说明符,有两种使用形式:

  1. noexcept:等价于noexcept(true),承诺函数绝对不抛出任何异常;
  2. noexcept(表达式):编译期判断表达式是否为true,决定是否承诺不抛异常(比如noexcept(std::is_nothrow_move_constructible_v<T>))。

它的核心价值:

  • 性能优化:编译器知道函数不抛异常后,会省略栈展开、异常处理的额外代码;
  • 行为控制:某些场景下(如容器移动),编译器会根据noexcept决定是否采用更高效的逻辑;
  • 异常安全承诺:向调用者明确函数的异常行为,简化异常处理逻辑。

noexcept的核心使用场景

  • 移动构造 / 移动赋值运算符

这是noexcept最常用、最关键的场景 ——标准库容器(如std::vectorstd::string)在扩容 / 移动时,只有当移动构造 / 赋值是noexcept时,才会选择 “移动” 而非 “拷贝”

原因:容器需要保证 “异常安全”—— 如果移动过程中抛出异常,容器可能处于不一致状态(比如部分元素移动、部分未移动),而拷贝构造能保证失败时原数据不受影响。因此编译器默认策略是:

1.移动构造 / 赋值加noexcept → 容器用移动(高性能);

2.移动构造 / 赋值不加noexcept → 容器退化为拷贝(低性能,但异常安全)。

  • 析构函数(默认noexcept,建议显式声明)

显式声明noexcept的意义:

1.代码可读性更好,明确告诉开发者 “析构不抛异常”;

2.避免因基类 / 成员析构的异常属性导致析构函数变为noexcept(false)(比如手动声明~MyClass() noexcept {},强制保证不抛异常)。

  • 纯静态 / 工具函数(无异常风险的函数)

对于那些逻辑简单、不可能抛出异常的函数(如数学计算、简单赋值、getter/setter),加noexcept能让编译器优化代码,同时明确异常行为。

绝对不要加noexcept的场景

noexcept是 “承诺”,如果函数实际抛出了异常,程序会直接调用std::terminate终止(无法捕获),因此以下场景绝对不能加:

  1. 可能抛出异常的函数:比如涉及文件 IO、网络请求、内存分配(new可能抛std::bad_alloc)、越界检查的函数;
  2. 拷贝构造 / 拷贝赋值:这类函数通常需要分配内存、复制数据,有抛异常风险(如new失败),不能加noexcept
  3. 需要异常传递的函数:比如业务逻辑中的错误处理函数(需要通过异常向上层传递错误)
不抛保证(No-throw)
  • 语法:用 noexcept关键字标记函数,承诺 “绝不抛异常”;
// 标记 noexcept,编译器可优化,且调用者无需处理异常intadd(int a,int b)noexcept{return a + b;// 纯逻辑运算,不可能抛异常}// 析构函数默认 noexcept,必须保证不抛异常classSafeClass{private:int* p =newint(10);public:~SafeClass()noexcept{delete p;// delete 不会抛异常}};
基本保证(Basic)
  • 核心:用 RAII 保证资源不泄漏,允许对象 “未完成操作” 但状态有效;
#include<iostream>#include<stdexcept>#include<memory>usingnamespace std;voidtest(){ unique_ptr<int> ptr {make_unique<int>(42)};throwruntime_error("Intentional error for testing");}intmain(){try{test();}catch(const runtime_error& e){ cout <<"Caught an exception: "<< e.what()<< endl;}// 资源已释放,无泄漏;但函数未完成预期操作(符合基本保证)return0;}
强保证(Strong)
  • 核心:用 “Copy-and-Swap(拷贝并交换)” 实现 —— 要么全成,要么全败;
#include<iostream>#include<vector>#include<stdexcept>#include<algorithm>// swapusingnamespace std;classMyData{private: vector<int> nums ={1,2,3};public:// 强保证:修改前先拷贝,异常不影响原数据voidmodify(){// 步骤1:拷贝原数据到临时对象(副本) vector<int> temp = nums;// 步骤2:在副本上修改(抛异常也不影响原数据) temp[0]=100;throwruntime_error("测试异常"); temp[1]=200;// 步骤3:交换副本和原数据(swap 是 no-throw 操作)swap(nums, temp);}voidprint(){for(int num : nums) cout << num <<" "; cout << endl;}};intmain(){ MyData data;try{ data.modify();}catch(const exception& e){ cout << e.what()<< endl;}// 异常后,原数据完全没变化({1,2,3})→ 强保证 data.print();return0;}

异常安全关键手段有 3 个:

用 RAII 管理所有资源

  • 替代所有 “手动资源管理”:用 unique_ptr/shared_ptr 管理堆内存,用 fstream 管理文件,用 lock_guard 管理锁;
  • 核心逻辑:RAII 对象在栈上,离开作用域必析构,资源必释放 —— 无论是否抛异常。

遵循 “先拷贝,后修改”(强保证)

  • 修改对象前,先在 “临时副本” 上完成所有操作;
  • 只有副本操作全部成功(无异常),才用 swap(no-throw 操作)替换原对象;
  • swap 必须标记 noexcept,否则强保证会失效。

关键函数标记 noexcept

  • 必须标记 noexcept 的函数:析构函数、移动构造 / 赋值、swap 函数、纯逻辑函数;
  • 原因:若它们抛异常,整个程序的异常安全会崩溃。

Read more

【花雕学编程】Arduino BLDC 之基于超声波与PID控制的简单跟随机器人

【花雕学编程】Arduino BLDC 之基于超声波与PID控制的简单跟随机器人

基于 Arduino 的无刷直流电机(BLDC)超声波与 PID 控制简单跟随机器人,是一个将经典自动控制理论与现代高效驱动技术相结合的典型机电一体化项目。该系统通过超声波传感器获取环境距离信息,利用 PID 算法实时解算运动指令,并由 Arduino 驱动 BLDC 电机执行,从而实现对目标物体的稳定、平滑跟随。 1、主要特点 三角测量与单发双收架构 这是实现“定向”跟随而非“盲目”避障的核心感知逻辑。 单发双收拓扑: 系统通常采用一个手持式超声波发射模块和两个安装在机器人前端左右两侧的接收模块(单发双收)。这种布局构成了一个简单的三角形测量系统。 偏差解算原理: 当目标(人)正对机器人时,左右两个接收模块测得的距离 ,系统可以精确判断目标的偏航角度,从而实现方向控制。 双环 PID 串级控制 为了实现平稳的跟随效果,系统通常采用速度环(内环)与方向环(外环)的串级 PID 控制结构。 方向环(

By Ne0inhk

修改base_model路径加载本地Stable Diffusion模型的方法

修改 base_model 路径加载本地 Stable Diffusion 模型的完整实践指南 在当前生成式 AI 快速落地的过程中,越来越多开发者和企业不再满足于“开箱即用”的通用模型,而是希望基于已有预训练模型进行个性化微调。Stable Diffusion 作为图像生成领域的标杆,其 LoRA(Low-Rank Adaptation)微调方案因其轻量高效、显存友好而广受欢迎。 然而,在实际部署中,一个看似简单却极易出错的操作——正确加载本地基础模型,往往成为项目推进的瓶颈。尤其是当网络受限、模型版本混乱或路径配置不当,整个训练流程可能在第一步就宣告失败。 本文将围绕 lora-scripts 等主流工具链中的 base_model 参数,深入剖析如何稳定、可靠地从本地加载 Stable Diffusion 模型,并结合工程实践给出可落地的最佳配置策略。 为什么必须手动指定 base_model 路径? 很多初学者习惯依赖自动化脚本自动下载模型,比如运行训练时由程序从 Hugging

By Ne0inhk

告别窗口混乱:QTTabBar让你的Windows资源管理器重获新生

告别窗口混乱:QTTabBar让你的Windows资源管理器重获新生 【免费下载链接】qttabbarQTTabBar is a small tool that allows you to use tab multi label function in Windows Explorer. https://www.yuque.com/indiff/qttabbar 项目地址: https://gitcode.com/gh_mirrors/qt/qttabbar 你是不是经常在桌面堆满了文件资源管理器窗口?每次找文件都要在十几个重叠的窗口间来回翻找,工作效率被严重拖累?今天我要为你推荐一款改变游戏规则的工具——QTTabBar,它将彻底革新你的Windows文件管理体验。 从混乱到有序:我的文件管理进化史 还记得那些日子吗?打开一个文件夹,再打开另一个,不知不觉间任务栏就挤满了资源管理器图标。想要对比两个文件夹的内容?抱歉,你得在两个窗口间反复切换。想要快速访问常用目录?

By Ne0inhk

dify接入企业微信群聊机器人详细步骤(从零到上线全记录)

第一章:dify接入企业微信群聊机器人详细步骤(从零到上线全记录) 准备工作:获取企业微信机器人Webhook URL 在企业微信管理后台创建群聊机器人,获取唯一的 Webhook 地址。该地址用于外部系统向指定群组发送消息。登录企业微信 → 进入“应用管理” → 创建或选择一个自建应用 → 添加“群机器人”,复制生成的 Webhook URL。 配置Dify工作流触发外部通知 在 Dify 中设置自定义响应后处理逻辑,通过 HTTP 请求将输出内容推送到企业微信群。使用内置的“HTTP 请求”节点,填写以下参数: * Method: POST * URL: 企业微信机器人的 Webhook 地址 * Body (JSON): 包含要发送的消息内容 { "msgtype": "text", "text"

By Ne0inhk