item14:update

This commit is contained in:
racaljk 2018-08-07 16:55:33 +08:00
parent 8064e00c1a
commit 2d9ae59c29

View File

@ -58,4 +58,15 @@ struct pair {
};
```
这些函数视情况**noexcept**:它们是否**noexcept**依赖于`noexcept`声明中的表达式是否**noexcept**。假设有两个**Widget**数组,不抛异常的交换数组前提是数组中的元素交换不抛异常。对于**Widget**的交换是否**noexcept**决定了对于`Widget`数组的交换是否**noexcept**,反之亦然。类似的,交换两个存放**Widget**的`std::pair`是否**noexcept**依赖于**Widget**的交换是否**noexcept**。事实上交换高层次数据结构是否**noexcept**取决于它的构成部分的那些低层次数据结构是否异常,这激励你只要可以就提供**noexcept swap**函数(译注:因为如果你的函数不提供**noexcept**保证,其它依赖你的高层次**swap**就不能保证**noexcept**)。
这些函数视情况**noexcept**:它们是否**noexcept**依赖于`noexcept`声明中的表达式是否**noexcept**。假设有两个**Widget**数组,不抛异常的交换数组前提是数组中的元素交换不抛异常。对于**Widget**的交换是否**noexcept**决定了对于`Widget`数组的交换是否**noexcept**,反之亦然。类似的,交换两个存放**Widget**的`std::pair`是否**noexcept**依赖于**Widget**的交换是否**noexcept**。事实上交换高层次数据结构是否**noexcept**取决于它的构成部分的那些低层次数据结构是否异常,这激励你只要可以就提供**noexcept swap**函数(译注:因为如果你的函数不提供**noexcept**保证,其它依赖你的高层次**swap**就不能保证**noexcept**)。
现在,我希望你能为**noexcept**提供的优化机会感到高兴,同时我还得让你缓一缓别太高兴了。优化很重要,但是正确性更重要。我在这个条款的开头提到**noexcept**是函数接口的一部分,所以仅当你保证一个函数实现在长时间内不会抛出异常时才声明**noexcept**。如果你声明一个函数为**noexcept**,但随即又后悔了,你没有选择。你只能从函数声明中移除**noexcept**(即改变它的接口),这理所当然会影响客户端代码。你可以改变实现使得这个异常可以避免,再保留原版本(不正确的)异常说明。如果你这么做,程序将会在异常离开这个函数时终止。或者你可以重新设计既有实现,改变实现后再考虑你希望它是什么样子。这些选择都不尽人意。
这个问题的本质是实际上大多数函数都是异常中立__exception neutral__的。这些函数自己不抛异常但是它们内部的调用可能抛出。此时异常中立函数允许那些抛出异常的函数在调用链上更进一步直到遇到异常处理程序而不是就地终止。异常中立函数决不应该声明为**noexcept**,因为它们可能抛出那种"让它们过吧"的异常(译注:也就是说在当前这个函数内不处理异常,但是又不立即终止程序,而是让调用这个函数的函数处理)异常。因此大多数函数都不应该被指定为**noexcept**。
然而,一些函数很自然的不应该抛异常,更进一步值得注意的是移动操作和**swap**——使其不抛异常有重大意义,只要可能就应该将它们声明为**noexcept**。老实说,当你确保函数决不抛异常的时候,一定要将它们声明为**noexcept**。
请注意我说的那些很自然不应该抛异常的函数实现。为了**noexcept**而扭曲函数实现达成目的是本末倒置。是把马放到马车前,是一叶障目不见泰山。是...选择你喜欢的比喻吧。如果一个简单的函数实现可能引发异常(即调用它可能抛出异常),而你为了讨好调用者隐藏了这个(即捕获所有异常,然后替换为状态码或者特殊返回值),这不仅会使你的函数实现变得复杂,还会让所有调用点的代码变得复杂。调用者可能不得不检查状态码或特殊返回值。而这些**复杂**的运行时开销(额外的分支,大的函数放入指令缓存)可以超出**noexcept**带来的性能提升,再加上你会悲哀的发现这些代码又难读又难维护。那是糟糕的软件工程化。
对于一些函数,使其成为**noexcept**是很重要的它们应当默认如是。在C++98构造函数和析构函数抛出异常是糟糕的代码设计——不管是用户定义的还是编译器生成的构造析构都是**noexcept**。因此它们不需要声明**noexcept**。(这么做也不会有问题,只是不合常规)。析构函数非隐式**noexcept**的情况仅当类的数据成员明确声明它的析构函数可能抛出异常(即,声明`noexcept(false)`)。这种析构函数不常见,标准库里面没有。如果一个对象的析构函数可能被标准库使用,析构函数又可能抛异常,那么程序的行为是未定义的。