C++ noexcept关键字详解:从语法到实战应用 C noexcept关键字详解从语法到实战应用一、noexcept关键字详解1、 引言2、 noexcept的基本语法2.1、 无条件noexcept声明2.2 、条件noexcept声明2.3、 noexcept运算符3、 noexcept的语义与行为3.1、 异常传播机制3.2 、与throw()的区别4、 noexcept在移动语义中的应用4.1 、移动构造函数的noexcept声明4.2 、移动赋值运算符5、标准库中的noexcept应用5.1 、类型特征检查5.2、 交换操作6、 noexcept与模板元编程6.1 、SFINAE与noexcept6.2 、条件编译7、 性能优化考虑7.1、 编译器优化机会7.2、 实际性能测试8、 最佳实践与注意事项8.1、 何时使用noexcept8.2、 何时避免noexcept8.3 、错误示例9、 C17/20/23中的增强9.1、 C17noexcept成为类型系统的一部分9.2 、C20concepts与noexcept9.3 、C23expected类型与noexcept10、总结二、代码示例一、noexcept关键字详解1、 引言在C异常处理机制中noexcept关键字是一个重要但容易被忽视的特性。自C11引入以来noexcept不仅用于声明函数不会抛出异常更在性能优化、移动语义和代码安全性方面发挥着关键作用。本文将深入探讨noexcept的语法、语义、应用场景及最佳实践帮助开发者更好地理解和运用这一特性。2、 noexcept的基本语法2.1、 无条件noexcept声明最简单的用法是声明函数不会抛出任何异常voidfunc()noexcept;// 函数不会抛出异常2.2 、条件noexcept声明C11允许根据编译期表达式决定函数是否noexcepttemplatetypenameTvoidswap(Ta,Tb)noexcept(noexcept(a.swap(b))){a.swap(b);}2.3、 noexcept运算符noexcept还可以作为运算符在编译期检查表达式是否可能抛出异常boolb1noexcept(func());// 检查func()是否可能抛出异常boolb2noexcept(T());// 检查T的默认构造是否可能抛出异常3、 noexcept的语义与行为3.1、 异常传播机制当noexcept函数内部抛出异常时程序会直接调用std::terminate()终止而不是向上传播异常voiddangerous()noexcept{throwstd::runtime_error(Oops!);// 直接terminate!}intmain(){dangerous();// 程序终止不会捕获到异常return0;}3.2 、与throw()的区别C98/03使用throw()声明不抛出异常但语义不同// C98/03风格voidold_style()throw();// 如果抛出异常调用unexpected()// C11风格voidnew_style()noexcept;// 如果抛出异常调用terminate()noexcept在性能上更优因为编译器不需要为异常传播生成额外代码。4、 noexcept在移动语义中的应用4.1 、移动构造函数的noexcept声明标准库容器如std::vector在重新分配内存时如果移动构造函数是noexcept的会优先使用移动而非复制classMyClass{public:// noexcept移动构造函数MyClass(MyClassother)noexcept:data_(std::move(other.data_)){}// 非noexcept移动构造函数MyClass(MyClassother)// 没有noexcept:data_(std::move(other.data_)){}private:std::vectorintdata_;};// 使用noexcept版本时vector重分配效率更高std::vectorMyClassvec;vec.push_back(MyClass());// 如果移动构造函数是noexcept效率更高4.2 、移动赋值运算符同样的原则也适用于移动赋值运算符classResource{public:Resourceoperator(Resourceother)noexcept{if(this!other){cleanup();data_std::move(other.data_);}return*this;}};5、标准库中的noexcept应用5.1 、类型特征检查标准库提供了std::is_nothrow_constructible等类型特征#includetype_traitsstructA{A()noexcept{}};structB{B(){throw1;}};static_assert(std::is_nothrow_constructibleA::value,A should be nothrow);static_assert(!std::is_nothrow_constructibleB::value,B should throw);5.2、 交换操作标准算法和容器使用std::swap时如果交换操作是noexcept的可以获得更好的异常安全保证namespacestd{templatetypenameTvoidswap(Ta,Tb)noexcept(noexcept(a.swap(b))){a.swap(b);}}6、 noexcept与模板元编程6.1 、SFINAE与noexceptnoexcept可以作为SFINAE的条件参与重载决议templatetypenameTautocall(Tt)noexcept(noexcept(t.func()))-decltype(t.func()){returnt.func();}// 当t.func()可能抛出异常时选择其他重载6.2 、条件编译根据noexcept特性选择不同的实现templatetypenameT,boolnoexcept(T().swap(T()))structSwapper;// noexcept版本templatetypenameTstructSwapperT,true{staticvoidswap(Ta,Tb)noexcept{a.swap(b);}};// 可能抛出版本templatetypenameTstructSwapperT,false{staticvoidswap(Ta,Tb){usingstd::swap;swap(a,b);}};7、 性能优化考虑7.1、 编译器优化机会noexcept函数为编译器提供了更多优化机会省略异常处理表不需要生成异常传播代码内联优化更可能被内联代码大小减少异常处理代码通常占比较大7.2、 实际性能测试#includechrono#includevector#includeiostreamclassNoexceptMove{public:NoexceptMove()default;NoexceptMove(NoexceptMove)noexceptdefault;};classThrowingMove{public:ThrowingMove()default;ThrowingMove(ThrowingMove){}// 可能抛出};templatetypenameTvoidtest_performance(){std::vectorTvec;autostartstd::chrono::high_resolution_clock::now();for(inti0;i1000000;i){vec.push_back(T{});}autoendstd::chrono::high_resolution_clock::now();autodurationstd::chrono::duration_caststd::chrono::milliseconds(end-start);std::coutTime: duration.count()ms\n;}8、 最佳实践与注意事项8.1、 何时使用noexcept移动操作移动构造函数和移动赋值运算符应该声明为noexcept交换操作swap函数通常应该是noexcept的析构函数析构函数默认就是noexcept的不要改变简单函数确定不会抛出异常的小函数8.2、 何时避免noexcept可能失败的函数如内存分配、文件操作、网络请求调用可能抛出异常的函数虚函数基类声明noexcept会限制所有派生类8.3 、错误示例// 错误可能抛出异常却声明noexceptvoid*allocate(size_t size)noexcept{returnnewchar[size];// new可能抛出std::bad_alloc}// 正确不声明noexcept或使用nothrow newvoid*allocate(size_t size)noexcept(false){returnnew(std::nothrow)char[size];}9、 C17/20/23中的增强9.1、 C17noexcept成为类型系统的一部分void(*fp)()noexceptnullptr;// 指向noexcept函数的指针9.2 、C20concepts与noexcepttemplatetypenameTconceptNothrowMovablestd::is_nothrow_move_constructible_vT;templateNothrowMovable Tvoidprocess(Tt)noexcept{// T的移动构造是noexcept的}9.3 、C23expected类型与noexceptstd::expectedint,std::stringcompute()noexcept{try{return42;}catch(...){returnstd::unexpected(error);}}10、总结noexcept关键字在C现代编程中扮演着重要角色异常安全明确函数异常行为提高代码可靠性性能优化为编译器提供优化提示特别是移动语义API设计作为接口契约的一部分指导使用者元编程参与类型系统和编译期计算二、代码示例#includeiostream#includestdexcept#includetype_traits#includevector#includestring// // 1. 基础 noexcept 函数// 内部throw直接 terminate无法外部catch// voidfunc_no_throw()noexcept{std::coutfunc_no_throw 正常执行\n;// throw std::runtime_error(违规抛异常程序直接终止);}voidfunc_can_throw(){throwstd::runtime_error(普通函数抛出异常);}// // 2. 条件 noexcept必须常量表达式禁止使用函数参数// 模板中用类型特征编译期可求值// templatetypenameTvoidconditional_noexcept_func(T/*val*/)noexcept(std::is_arithmetic_vT){ifconstexpr(!std::is_arithmetic_vT){throwstd::logic_error(非算术类型抛出异常);}std::cout条件noexcept算术类型不抛异常\n;}// // 3. noexcept 运算符编译期查询表达式是否无异常// voidtest_noexcept_operator(){usingnamespacestd;coutboolalpha;coutnoexcept(func_no_throw()) noexcept(func_no_throw())endl;coutnoexcept(func_can_throw()) noexcept(func_can_throw())endl;coutnoexcept(1 2) noexcept(12)endl;}// // 4. 类特殊成员函数 noexcept容器移动性能关键// C11 析构函数默认 noexcept(true)// structDataObj{int*ptrnullptr;DataObj():ptr(newint(100)){}// 拷贝构造DataObj(constDataObjother){ptrnewint(*other.ptr);std::cout【拷贝构造】\n;}// 移动构造 noexceptvector扩容会走移动而非拷贝DataObj(DataObjother)noexcept{ptrother.ptr;other.ptrnullptr;std::cout【noexcept 移动构造】\n;}// 移动赋值 noexceptDataObjoperator(DataObjother)noexcept{if(this!other){deleteptr;ptrother.ptr;other.ptrnullptr;}return*this;}~DataObj()noexcept{deleteptr;}};voidtest_vector_move(){std::vectorDataObjvec;vec.reserve(1);vec.emplace_back();vec.emplace_back();}// // 5. Lambda noexcept// voidtest_lambda_noexcept(){autolam_throw[](){throw1;};autolam_no_throw[]()noexcept{std::coutlambda noexcept\n;};// 正确常量表达式不要写 noexcept(x0)autolam_const_cond[]()noexcept(true){};std::coutnoexcept(lam_no_throw())\n;}// // 6. C17 noexcept 函数指针区分不能同名函数重载// voidtest_func_ptr_noexcept(){usingFuncNoThrowvoid(*)()noexcept;usingFuncMayThrowvoid(*)();FuncNoThrow p1func_no_throw;FuncMayThrow p2func_can_throw;p1();}// // 7. 模板利用 noexcept 运算符做编译期分支判断// 替代非法的 noexcept 同名重载// templatetypenameFvoiddispatch_call(Ff){ifconstexpr(noexcept(f())){std::cout分发调用 noexcept 版本逻辑\n;f();}else{std::cout分发调用允许抛异常版本逻辑\n;try{f();}catch(...){}}}// // 主函数// intmain(){std::cout 1. 基础 noexcept 函数 \n;try{func_no_throw();}catch(conststd::exceptione){std::cerre.what()\n;}std::cout\n 2. 条件 noexcept 模板 \n;try{conditional_noexcept_func(123);conditional_noexcept_func(std::string{});}catch(...){std::cout捕获条件抛出异常\n;}std::cout\n 3. noexcept 运算符 \n;test_noexcept_operator();std::cout\n 4. vector 移动构造优化 \n;test_vector_move();std::cout\n 5. Lambda noexcept \n;test_lambda_noexcept();std::cout\n 6. noexcept 函数指针 \n;test_func_ptr_noexcept();std::cout\n 7. 编译期 noexcept 分支分发 \n;dispatch_call(func_no_throw);dispatch_call(func_can_throw);return0;}