news 2026/4/13 22:20:52

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/4/13 8:38:55

常见类后续,泛型,文件

引言 常见类后续较为重要的大体可以概述为“集合List","字典Dictionary",二者均为容器对象&#xff08;存储多个元素&#xff09;&#xff0c;二者异中有同 List集合: 可变长度(扩容), 提供丰富方法, 元素可重复 字典: 可变长度(扩容),提供丰富方法, key-value…

作者头像 李华
网站建设 2026/4/13 13:35:34

yaml-cpp内存优化策略深度解析:从性能瓶颈到高效解决方案

yaml-cpp内存优化策略深度解析&#xff1a;从性能瓶颈到高效解决方案 【免费下载链接】yaml-cpp A YAML parser and emitter in C 项目地址: https://gitcode.com/gh_mirrors/ya/yaml-cpp 在C项目开发中&#xff0c;YAML配置文件的解析性能往往成为系统瓶颈&#xff0c;…

作者头像 李华
网站建设 2026/4/9 1:09:57

JSP如何结合多线程技术提升大文件上传效率?

大文件传输解决方案技术提案 项目背景与需求分析 作为山东某软件公司项目负责人&#xff0c;我公司需要为大文件传输提供一套完整的解决方案。经过详细的需求梳理&#xff0c;总结出以下几个关键需求点&#xff1a; 大文件传输能力&#xff1a;支持单文件100GB左右的上传下载…

作者头像 李华
网站建设 2026/4/12 18:39:21

ChanlunX缠论插件:让技术分析变得简单直观的智能助手

你是否曾在K线图中迷失方向&#xff1f;面对密密麻麻的K线图&#xff0c;是否感到无从下手&#xff1f;&#x1f914; 今天&#xff0c;让我们一起来了解ChanlunX缠论插件如何通过智能化算法&#xff0c;将复杂的技术分析变得简单直观。 【免费下载链接】ChanlunX 缠中说禅炒股…

作者头像 李华
网站建设 2026/4/5 0:35:19

光伏储能系统搭上虚拟同步发电机(VSG)这趟车,简直像是给新能源装了个智能大脑。今儿咱们直接上硬菜,拆解这个能跑出完美波形的并网仿真模型

光伏储能虚拟同步发电机VSG并网仿真模型C 光伏阵列搭建的光伏电池模型 光伏&#xff1a;采用扰动观察法最大功率点MPPT跟踪控制 储能&#xff1a;蓄电池充放电控制&#xff0c;双向Buck/Boost变换器&#xff0c;采用直流母线电压外环控制稳定直流母线电压&#xff0c;电池电流内…

作者头像 李华