noexcept说明
在C++11之后,表示函数不会抛出异常的动态异常声明throw()被新的noexcept异常声明所取代。
该关键字告诉编译器,函数中不会发生异常,这有利于编译器对程序做更多的优化。
如果在运行时,noexecpt函数向外抛出了异常(如果函数内部捕捉了异常并完成处理,这种情况不算抛出异常),程序会直接终止,调用std::terminate()函数,该函数内部会调用std::abort()终止程序。
从语法上讲,noexcept修饰符有两种形式,一种就是简单地在函数声明后加上noexcept关键字。比如:
void excpt_func() noexcept;
另外一种则可以接受一个常量表达式作为参数,如下所示:
void excpt_func() noexcept (常量表达式);
抛出异常与栈展开(stack unwinding)
抛出异常时,将暂停当前函数的执行,开始查找匹配的catch子句。首先检查throw本身是否在try块内部,如果是,检查与该try相关的catch子句,看是否可以处理该异常。如果不能处理,就退出当前函数,并且释放当前函数的内存并销毁局部对象,继续到上层的调用函数中查找,直到找到一个可以处理该异常的catch。这个过程称为栈展开(stack unwinding)。当处理该异常的catch结束之后,紧接着该catch之后的点继续执行。
- 为局部对象调用析构函数
如上所述,在栈展开的过程中,会释放局部对象所占用的内存并运行类类型局部对象的析构函数。但需要注意的是,如果一个块通过new动态分配内存,并且在释放该资源之前发生异常,该块因异常而退出,那么在栈展开期间不会释放该资源,编译器不会删除该指针,这样就会造成内存泄露。
- 析构函数应该从不抛出异常
在为某个异常进行栈展开的时候,析构函数如果又抛出自己的未经处理的另一个异常,将会导致调用标准库terminate函数。通常terminate函数将调用abort函数,导致程序的非正常退出。所以析构函数应该从不抛出异常。
- 异常与构造函数
如果在构造函数对象时发生异常,此时该对象可能只是被部分构造,要保证能够适当的撤销这些已构造的成员。
- 未捕获的异常将会终止程序
不能不处理异常。如果找不到匹配的catch,程序就会调用库函数terminate。
验证
#include <iostream>
using namespace std;
struct A {
A() { cout << "constructor" << endl; }
~A() { cout << "destructor" << endl; }
};
void func1() noexcept
{
A a1;
A a2;
}
void func2()
{
A a1;
A a2;
}
int main()
{
func1();
func2();
return 0;
}
# gcc -std=c++11
(gdb) disassemble func1
(gdb) disassemble func2
可以看出加了noexcept的函数没有增加_Unwind_Resume调用,汇编代码更短小,为编译优化增加更多可能。
什么时候该使用noexcept
使用noexcept表明函数或操作不会发生异常,会给编译器更大的优化空间。然而,并不是加上noexcept就能提高效率,步子迈大了也容易扯着蛋。
以下情形鼓励使用noexcept:
-
移动构造函数(move constructor)
-
移动分配函数(move assignment)
-
析构函数(destructor)。这里提一句,在新版本的编译器中,析构函数是默认加上关键字noexcept的。
-
叶子函数(Leaf Function)。叶子函数是指在函数内部不分配栈空间,也不调用其它函数,也不存储非易失性寄存器,也不处理异常。
最后强调一句,在不是以上情况或者没把握的情况下,不要轻易使用noexception。 -
每个函数都考虑noexcept会很麻烦,所以只在明显的时候使用
-
现在编译器在好路径上异常没有影响,noexcept可能的作用是减小体积
-
推荐在构造、复制等常用操作标记noexcept,这样性能提升可能会比较大。例如vector不会使用你的类move操作,除非它被标记为noexcept(有的编译器能自动推导)
-
noexcept主要是给使用者看的,对编译器影响不大
异常说明与指针、虚函数和拷贝控制
- 函数指针及该指针指向的函数必须具有一致的异常说明。如果一个指针做出了不抛出异常的声明,则该指针将只能指向不抛出异常的函数。
- 如果显示或隐式说明了指针可能抛出异常,那么该指针可以指向任何函数。
- 如果一个虚函数承诺不会抛出异常,则后续派生出来的衍生类的虚函数也必须做出同样的承诺。反之则不需要。
- 当编译器合成拷贝控制成员的时候,也会生成一个异常说明。如果对所有成员和基类的所有操作都承诺了不抛出异常,则合成的成员是noexcept的;如果有任意一个函数可能抛出异常,则合成的成员是noexcept(false)。
参考
http://liubigbin.github.io/2016/07/06/C-11%E5%B8%B8%E8%A7%84%E7%89%B9%E6%80%A7%E4%B9%8Bnoexcept/
https://www.jianshu.com/p/08a53d8c9670
https://www.cnblogs.com/zhuyf87/archive/2012/12/23/2829725.html
https://www.cnblogs.com/catch/p/3604516.html
https://www.cnblogs.com/catch/p/3619379.html
https://www.cnblogs.com/sword03/p/10020344.html
https://www.zhihu.com/question/30950837
https://stackoverflow.com/questions/10787766/when-should-i-really-use-noexcept