为何不将不会抛异常的函数声明为noexcept?
摘要:本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误。谢谢! 博客已经迁移到 "这里啦" 在C&
本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误。谢谢!
博客已经迁移到这里啦
在C++98中,异常规范(exception specifications)是一个不稳定因素。你必须总结出一个函数可能会抛出的异常类型,所以如果函数的实现被修改了,异常规范可能也需要被修正。改变异常规范则又可能影响到客户代码,因为调用者可能依赖于原先的异常规范。编译器通常不会提供帮助来维护“函数实现,异常规范以及客户代码”之间的一致性。最终,大多数程序员觉得C++98的异常规范不值得去使用。
C++11中,对于函数的异常抛出行为来说,出现了一种真正有意义的信息,它能说明函数是否有可能抛出异常。是或不是,一个函数可能抛出一个异常或它保证它不会抛出异常。这种“可能或绝不”二分的情况是C++11异常规范的基础,这种异常规范从本质上替换了C++98的异常规范。(C++98风格的异常规范仍然是有效的,但是它们是被弃用了的。)在C++11中,无条件的noexcept就说明这个函数保证不会抛出异常。
在设计接口的时候,一个函数是不是应该这么声明(noexcept)是一个需要考虑的问题。函数的异常抛出行为是客户最感兴趣的部分。调用者能询问一个函数的noexcept状态,并且这个询问的结果能影响异常安全(exception safety)或着调用代码的性能。因此,一个函数是否是noexcept和一个成员函数是否是cosnt,这两个信息使同样重要。当你知道一个函数不会抛出异常的时候却不声明它为noexcept,就属于一个不好的接口设计。
但是,这里还有一个额外的动机让我们把noexcept应用到不会产生异常的函数上:它允许编译器产生更好的目标代码。为了理解为什么会这样,让我们检查一下C++98和C++11中,对于一个函数不会抛出异常的不同解释。考虑一个函数f,它保证调用者永远不会收到异常。两种不同的表示方法:
int f(int x) throw(); //C++98风格
int f(int x) noexcept; //C++11风格
如果,运行时期,一个异常逃离了f,这违反了f的异常规范。在C++98的异常规范下,f的调用者的调用栈被解开了,然后经过一些不相关的动作,程序终止执行。在C++11的异常规范下,运行期行为稍微有些不同:调用栈只有在程序终止前才有可能被解开。
解开调用栈的时机,以及解开的可能性的不同,对于代码的产生有很大的影响。在一个noexcept函数中,如果一个异常能传到函数外面去,优化器不需要保持运行期栈为解开的状态,也不需要确保noexcept函数中的对象销毁的顺序和构造的顺序相反(译注:因为noexcept已经假设了不会抛出异常,所以就算异常被抛出,大不了就是程序终止,而不可能处理异常)。使用“throw()”异常规范的函数,以及没有异常规范的函数,没有这样的优化灵活性。三种情况能这样总结:
RetType function(params) noexcept; //优化最好
RetType function(params) throw(); //没有优化
RetType function(params); //没有优化
这种情况就能作为一个充足的理由,让你在知道函数不会抛出异常的时候,把它声明为noexcept。
对于一些函数,情况变得更加强烈(更多的优化)。move操作就是一个很好的例子。假设你有一份C++98代码,它使用了std::vector。Widget通过一次次push_back来加到std::vector中:
std::vector<Widget> vw;
...
Widget w;
... //使用w
vw.push_back(w); //把w加到vw中
...
假设这个代码工作得很好,然后你也没有兴趣把它改成C++11的版本。但是,基于C++11的move语法能提升原来代码的性能(当涉及move-enabled类型时)的事实,你想做一些优化,因此你要保证Widget有一个move operation,你要么自己写一个,要么用函数生成器来实现(看Item 17)。
当一个新的元素被添加到std::vector时,可能std::vector剩下的空间不足了,也就是std::vector的size等于它的capacity(容量)。当发生这种事时,std::vector申请一个新的,更大的内存块来保存它的元素,然后把原来的内存块中的元素,转移到新块中去。在C++98中,转移是通过拷贝来完成的,它先把旧内存块中的所有元素拷贝到新内存块中,再销毁旧内存块中的对象(译注:再delete旧内存)。
