news 2026/4/3 6:08:39

C++中noexcept关键字提出动机和使用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++中noexcept关键字提出动机和使用

一、noexcept是为了解决什么问题?

在 C++11 之前,异常说明使用的是动态异常规范

voidf()throw(int,std::bad_alloc);voidg()throw();// 表示不抛异常

问题极其严重:

  1. 运行期检查,零优化空间
  2. 违反即调用unexpected(),再terminate()
  3. ABI 不稳定,编译器难以优化
  4. STL 无法据此做容器级别决策

几乎没人敢用


C++11 的设计目标

noexcept的核心动机是:

让“是否会抛异常”成为一个可在编译期推导、可用于优化、可影响接口选择的属性

换句话说:

异常是否发生,从「运行期契约」升级为「类型系统的一部分」


二、noexcept的本质语义(非常重要)

voidf()noexcept;

并不是说:

“这个函数不会抛异常”

而是说:

如果这个函数抛异常,程序将立刻调用std::terminate()

即:

try{f();}catch(...){std::terminate();// 无条件}

noexcept承诺,不是能力检测。


三、noexcept的两种形式

1.无条件noexcept

voidf()noexcept;

等价于:

voidf()noexcept(true);

2.条件noexcept(C++11 核心设计)

template<typenameT>voidfoo(T&&x)noexcept(noexcept(T(std::forward<T>(x))));

异常规格成为编译期表达式

示例:完美转发构造

template<typenameT>Tmake()noexcept(noexcept(T())){returnT();}

四、noexcept是类型系统的一部分

voidf()noexcept;voidg();usingF=void(*)();usingNF=void(*)()noexcept;F pf=g;// OKNF pnf=f;// OK

但:

NF pnf=g;// 编译错误

noexcept是函数类型签名的一部分。


五、为什么noexcept对性能至关重要?

1.影响代码生成(EH tables)

  • 有异常 → 生成异常展开表
  • noexcept完全移除异常元数据

在 hot loop / 数值计算 / SLAM 后端中尤为关键。


2.STL 的核心决策依据

std::vector扩容行为

if(T isnoexcept-move-constructible)使用 moveelse使用 copy

等价于:

std::is_nothrow_move_constructible_v<T>

示例:为什么没写noexcept会导致性能灾难

structBad{Bad(Bad&&){}// 没有 noexcept};structGood{Good(Good&&)noexcept{}};
std::vector<Bad>v1;// 扩容时 copystd::vector<Good>v2;// 扩容时 move

这就是 STL 要求 move ctornoexcept的原因


六、noexcept与移动语义的关系(核心)

Rule of Five + noexcept

structX{X(X&&)noexcept=default;X&operator=(X&&)noexcept=default;};

原因

  • vector,deque,map等容器
  • std::optional,std::variant
  • std::unique_ptr

全部依赖noexcept来选择移动路径


七、noexcept与析构函数(极其重要)

C++11 起

~T()noexcept(true);// 默认

即:

析构函数隐式noexcept

如果析构函数抛异常?

~T(){throwstd::runtime_error("boom");}

std::terminate()

原因:防止 stack unwinding 二次异常


正确模式

~T()noexcept{try{cleanup();}catch(...){log_error();}}

八、noexcept与模板元编程

常见 trait

std::is_nothrow_move_constructible<T>std::is_nothrow_copy_constructible<T>std::is_nothrow_destructible<T>

典型应用(SLAM / 点云库中很常见)

template<typenameT>voidsafe_swap(T&a,T&b)noexcept(std::is_nothrow_move_constructible_v<T>&&std::is_nothrow_move_assignable_v<T>){T tmp=std::move(a);a=std::move(b);b=std::move(tmp);}

九、noexceptvsconst

属性是否属于类型
const
noexcept
throw()否(已废弃)

十、常见误区(非常重要点)

误区 1:noexcept= 不会抛异常

事实
noexcept的语义是“一旦抛异常,立即std::terminate()


错误理解示例

#include<iostream>#include<stdexcept>voidf()noexcept{std::cout<<"before throw\n";throwstd::runtime_error("boom");std::cout<<"after throw\n";}intmain(){f();}

运行结果

before throw terminate called after throwing an instance of 'std::runtime_error'
  • catch根本来不及
  • 栈不会正常展开
  • 析构函数不会全部执行

对比:非noexcept

voidg(){throwstd::runtime_error("boom");}intmain(){try{g();}catch(conststd::exception&e){std::cout<<"caught: "<<e.what()<<'\n';}}

输出

caught: boom

正常异常语义


工程结论

noexcept是“强终止契约”,不是“不会抛”的保证


误区 2:随便给函数加noexcept

这是生产事故级错误


错误示例:包装函数

voidmay_throw(){throwstd::runtime_error("error");}voidwrapper()noexcept{may_throw();//}intmain(){wrapper();}

运行结果

terminate called after throwing an instance of 'std::runtime_error'

更隐蔽的版本(真实工程坑)

voidlog(conststd::string&s){if(s.empty()){throwstd::logic_error("empty");}}voidfoo()noexcept{log("");// 间接抛异常}

根本看不到 throw,却直接 terminate


正确写法 1:内部吞异常

voidfoo()noexcept{try{log("");}catch(...){// fallback / logging}}

正确写法 2:条件noexcept

template<typenameF>voidcall(F&&f)noexcept(noexcept(f())){f();}

工程结论

只有当“整个调用链都不抛异常”时,才可以写noexcept


误区 3:忘记给 move ctor 加noexcept

这是 STL 性能退化最常见的来源


错误示例

#include<vector>structBad{Bad()=default;Bad(constBad&)=default;Bad(Bad&&){}// 没有 noexcept};intmain(){std::vector<Bad>v;v.reserve(1);v.emplace_back();v.emplace_back();// 触发扩容}

STL 的真实逻辑

if(is_nothrow_move_constructible<T>)moveelsecopy

结果

  • 扩容时调用 copy ctor
  • 大对象 →灾难性性能
  • 对 Eigen / 点云 / 位姿对象尤其致命

正确示例

structGood{Good()=default;Good(constGood&)=default;Good(Good&&)noexcept{}//};

对比验证(可加日志)

structVerbose{Verbose()=default;Verbose(constVerbose&){std::cout<<"copy\n";}Verbose(Verbose&&)noexcept{std::cout<<"move\n";}};
std::vector<Verbose>v;v.emplace_back();v.emplace_back();

输出

move

如果去掉noexcept,输出是:

copy

工程级总结

一个没写noexcept的 move ctor,等价于“禁用移动语义”


三个误区一句话总结

误区本质错误
noexcept= 不会抛实际是“抛了就死”
随便加noexcept违反调用链异常安全
move ctor 没noexceptSTL 主动退化到 copy

十一、工程级使用准则

必须noexcept

场景
移动构造 / 移动赋值
析构函数
swap
RAII cleanup
数值内核、实时系统

谨慎使用

场景
构造函数(分配内存)
IO
用户回调

不要使用

场景
无法保证内部调用链不抛异常

十二、一个完整工程示例

structPose{Eigen::Matrix4d T;Pose()=default;Pose(Pose&&other)noexcept:T(std::move(other.T)){}Pose&operator=(Pose&&other)noexcept{T=std::move(other.T);return*this;}~Pose()noexcept=default;};

这类类型在SLAM 后端、图优化、点云容器中是黄金标准


十三、总结一句话

noexcept不是语法糖,而是现代 C++ 性能、异常安全和库设计的核心支点

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/25 17:06:16

设备预测性维护技术实战:云边端一体化落地与中讯烛龙系统推荐

在工业数字化加速的当下&#xff0c;设备预测性维护技术&#xff08;PdM&#xff09;正从“锦上添花”走向“能力底座”。它以状态数据为核心&#xff0c;借助机器学习与边缘计算&#xff0c;在故障发生前给出可执行的维护窗口&#xff0c;帮助企业实现从被动抢修到主动预防的跃…

作者头像 李华
网站建设 2026/3/18 0:58:26

软件打开出现找不到Vfp6rchs.dll文件 丢失的情况 下载修复

在使用电脑系统时经常会出现丢失找不到某些文件的情况&#xff0c;由于很多常用软件都是采用 Microsoft Visual Studio 编写的&#xff0c;所以这类软件的运行需要依赖微软Visual C运行库&#xff0c;比如像 QQ、迅雷、Adobe 软件等等&#xff0c;如果没有安装VC运行库或者安装…

作者头像 李华
网站建设 2026/3/30 9:54:46

【30天从零学Python】重要补充四、检测有向环 - Kahn算法

30天从零学Python 通信工程专业科班生&#xff0c;用了几十年MATLAB&#xff0c;为了过大厂机考&#xff0c;不得不自学Python。 文章目录30天从零学Python重要补充四、检测有向环 - Kahn算法1. 有向环与拓扑排序1.1 Kahn 算法核心原理&#xff08;通俗版&#xff09;1.2 Kahn…

作者头像 李华
网站建设 2026/3/31 17:43:25

Flutter工程化实战:从单人开发到团队协作的规范与效率指南

Flutter工程化实战&#xff1a;从单人开发到团队协作的规范与效率指南 欢迎大家加入开源鸿蒙跨平台开发者社区&#xff0c;一起共建开源鸿蒙跨平台生态。 当Flutter项目从“单人小demo”升级为“多人协作的中大型项目”时&#xff0c;单纯依靠“编码能力”已无法保障项目质量—…

作者头像 李华
网站建设 2026/4/1 7:47:12

Flutter 与其他跨平台框架的核心差异分析

欢迎大家加入开源鸿蒙跨平台开发者社区&#xff0c;一起共建开源鸿蒙跨平台生态。### Flutter 框架对比分析&#xff1a;技术视角与代码案例 Flutter 与其他跨平台框架的核心差异分析 架构层面的根本区别 Flutter 采用独特的自渲染架构&#xff0c;其核心由以下组件构成&…

作者头像 李华