自C++98起,异常处理机制已成为C++的一部分,通过throw关键字抛出异常,而try-catch块则用于捕获并处理这些异常。然而,在C++98时代,异常处理的设计理念较为严格,捕获语句无法捕获未知的异常,进而要求函数修改异常时,调用该函数的父类函数的异常捕获语句也需要做出适配性修改,否则异常捕获语句失效。因此,C++程序员很少使用try-catch进行异常捕获——未知的异常无法捕获,已知的异常没必要捕获。
C++11后,逐渐形成“函数要么可能发射异常,要么保证不会发生异常”的共识。并提出了关键字noexcept用于指明函数保证自己不会发生异常。
用法
noexcept既可以表征普通函数不发射异常,也可以用于表征成员函数不发射异常。
//普通函数
int add(int a, int b)noexcept
{
return a+b;
}
//成员函数
class People {
public:
People(std::string name, int age) :m_name{ name }, m_age{ age } {}
~People()noexcept = default;
inline const int GetAge()const noexcept
{
return m_age;
}
inline const std::string GetName()const noexcept
{
return m_name;
}
private:
std::string m_name{ "" };
int m_age{ 0 };
};
原理剖析
noexcept保证函数不会发射异常,那么noexcept是如何保证的呢?为了分析这个问题,不妨让noexcept函数抛出异常,同时让普通函数抛出异常作为对照组,对比分析两个函数的行为。验证代码及行为如下:
//当noexcept函数触发异常时,会直接在函数内抛出异常的位置中断,异常未扩散。
//已在 xxx.exe 中执行断点指令(__debugbreak()语句或类似调用)。
void no_exception()noexcept
{
throw true;
}
//当常规函数触发异常时会提示异常;
//0x00007FFA2D8F543C 处(位于 xxxx.exe 中)有未经处理的异常:
// Microsoft C++ 异常: bool,位于内存位置 0x0000005B28B3F444 处。
void exception()
{
throw true;
}
由如上行为可知,noexcept函数在触发异常时直接中断,异常自然无法向外发射(传递)。
正是由于其不向外发射异常特性,为编译器提供了更大的舞台。
注意事项
补充
noexcept可以作为操作符,用于检测函数是否被标记为noexcept,使用代码如下:
void no_exception()noexcept
{
throw true;
}
void exception()
{
throw true;
}
void myfunc()
{
int a=10;
int b=10;
exception();
int c= a+b;
}
int test_noexcept_oper() {
std::cout<<std::boolalpha<<noexcept(no_exception())<<"\n"
<<noexcept(exception())<<"\n"
<<noexcept(myfunc())<<"\n";
return 0;
}
总结
本文回顾了C++98以来的异常处理机制,并介绍了C++11中的noexcept关键字,借助示例代码说明了其用法、剖析了其原理,并列举了使用时的注意事项;同时说明了noexcept作为操作符的用法。
感谢您的关注、点赞。