EffectiveModernCppChinese/3.MovingToModernCpp/item14.md
2018-06-13 09:40:34 +08:00

36 lines
3.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## Item 14:如果函数不抛出异常请使用noexcept
条款 14:如果函数不抛出异常请使用noexcept
在C++98中异常说明exception specifications是喜怒无常的野兽。你不得不写出函数可能抛出的异常类型如果函数实现有所改变异常说明也可能需要修改。改变异常说明会影响客户端代码因为调用者可能依赖原版本的异常说明。编译器不会为函数实现异常说明和客户端代码中提供一致性保障。大多数程序员最终都认为不值得为C++98的异常说明如此麻烦。
在C++11标准化过程中大家一致认为异常说明真正有用的信息是一个函数是否会抛出异常。非黑即白一个函数可能抛异常或者不会。这种"可能-绝不"的二元论构成了C++11异常说的基础从根本上改变了C++98的异常说明。C++98风格的异常说明也有效但是已经标记为deprecated废弃。在C++11中无条件的**noexcept**保证函数不会抛出任何异常。
关于一个函数是否已经声明为**noexcept**是接口设计的事。函数的异常抛出行为是客户端代码最关心的。调用者可以查看函数是否声明为**noexcept**,这个可以影响到调用代码的异常安全性和效率。
就其本身而言,函数是否为**noexcept**和成员函数是否**const**一样重要。如果知道这个函数不会抛异常就加上**noexcept**是简单天真的接口说明。
不过这里还有给不抛异常的函数加上**noexcept**的动机:它允许编译器生成更好的目标代码。
要想知道为什么了解C++98和C++11指明一个函数不抛异常的方式是很有用了。考虑一个函数**f**,它允许调用者永远不会受到一个异常。两种表达方式如下:
```cpp
int f(int x) throw(); // C++98风格
int f(int x) noexcept; // C++11风格
```
如果在运行时,**f**出现一个异常,那么就和**f**的异常说明冲突了。在C++98的异常说明中调用栈会展开至**f**的调用者一些不合适的动作比如程序终止也会发生。C++11异常说明的运行时行为明显不同调用栈只是_可能_在程序终止前展开。
展开调用栈和_可能_展开调用栈两者对于代码生成code generation有非常大的影响。在一个**noexcept**函数中,当异常传播到函数外,优化器不需要保证运行时栈的可展开状态,也不需要保证**noexcept**函数中的对象按照构造的反序析构。而"**throw()**"标注的异常声明缺少这样的优化灵活性,它和没加一样。可以总结一下:
```cpp
RetType function(params) noexcept; // 极尽所能优化
RetType function(params) throw(); // 较少优化
RetType function(params); // 较少优化
```
这是一个充分的理由使得你当知道它不抛异常时加上**noexcept**。
还有一些函数让这个案例更充分。移动操作是绝佳的例子。假如你有一份C++98代码里面用到了`std::vector<Widget>`。**Widget**通过**push_back**一次又一次的添加进`std::vector`
```cpp
std::vector<Widget> vw;
Widget w;
// work with w
vw.push_back(w); // add w to vw
```