exception
1569字约5分钟
2024-11-02
1.标准库之前一直遵守lakos规则。 什么是lakos规则? 被标记为窄契约的函数,不该标记为noexcept,即使已知他真的不会抛异常
有提案要求远离,3155要求继续遵守
什么是lakos规则
1.什么是narrow ,widen contract? Wide contract functions have well-defined behavior for all possible inputs, while narrow contracts mean that the functions can only be called when certain preconditions are met. 宽合同对所有可能的输入都有良定义行为,窄合同只有满足某些前提条件才能调用 例如vector的成员函数size()是宽契约,没有前提条件对任何即使被移动后的vector也能使用size()是定义明确的,另外如at()函数对不满足的范围的参数通过抛异常也就是所谓检查,而operator[]则是窄契约调用他的参数需要在范围内否则会引发未定义行为。 2.什么是合同(contract)?使用动机,如何使用,设计历史参考D2899R0 合同是类或函数的正式接口规范,他包含了对法律合同的条件和义务这一概念的隐喻表述。也就是说合同里声明了我需要负责的义务(条件)以及我对满足条件后期望获得的回报。为了和软件中的其他组件互操作我们规定合同。 当函数运行时不满足合同的条件,即会产生bug,这与错误不同。错误可以恢复,但合同需要修改源代码来解决 例如 int unsafe_acess(int i){return arr[i];}
当i小于0或大于max-1时此访问是未定义行为。 通常来说契约有前提条件,这些前提条件需要调用者满足,如上诉函数的输入需要调用者不能传递超过数组边界的大小。 而满足后置条件的责任,则由调用方来掌握。当函数执行时函数返回值,或被修改对象永远保持为真的状态。 如同
为什么要遵守lakos规则,设计初衷
noexcept关键字意在解决依赖“移动操作不应抛出异常”这个原则所写出代码的优化,因为多数0x c++编译器在类没有提供移动构造时复制构造作为备选项那么此时默认构造是允许抛出异常的。因为如果允许移动构造抛出异常那么类的不变式可能被打破,同样许多库依赖强异常安全保证(历史原因)。此时委员会提出一项策略新加操作符用来检测所有可观测的操作都是语言内置的并且不抛出(语言内置的操作抛不抛出跟库作者没关系了,只要语言部分保证不抛出那么肯定不跑出),或加上noexcept说明此函数不能抛出异常。注意到异常抛出时需要在异常抛出点和调用点添加额外栈回溯的代码,这个策略允许一项优化:抑制产生这些额外栈回溯的代码,减少二进制体积。因为我在语义上假设保证不会抛异常,自然也不需要处理异常的额外代码。 在2011年noexcept关键字很新还没有一项指导策略,同时盲目使用noexcept关键字的结果是,当被标记为noexcept函数真的抛出异常时程序直接终止这不被大部分用户所接受
被标记为noexcept的函数很难被测试,这里的可以引入成员函数std::string::at说明 没有前置条件,但它会抛出异常以指示下标越界。这里 提案作者举了一个例子,在驱动程序的函数中进入函数前需要判断前提条件需要进行断言,如之前的at函数下标,front函数需要检测容器不为空之类的。作者认为一个常见实践是测试这类断言是实现一个异常处理程序,使得断言抛出一个实现良好定义的违反前提条件的异常从而被捕获,从而测试是否断言在此处是否有效。作者这类防御性的编程是常见的,设置断言防止用户的错误调用。同样还有一些安全stl的实现能检测迭代器失效等前提条件。总的来说作者认为noexcept标记的函数阻止了一些进行通过抛异常来检测违反前提条件断言的常见实践。
提案作者希望通过设置断言的防御性编程的实践是:为断言宏提供异常处理函数,测试库的时候提供断言宏。将控制流返回给测试的驱动程序,同时附带抛异常的上下文例如 function, FILE and LINE ,存在意外报错的断言和缺失的断言必须当测试用例返回后,被报告。这项技术最简单实现是使用异常抛出并捕获,作者认为这项实践转到c++0x编译器与现有异常规定不兼容。 作者给出一下三种解决办法 1.有条件的将noexcept定义为宏 2.在标准库范围中使用宏代替关键字noexcept,但这样测试中禁用noexcept的异常规范会导致和正式行为不同,移动构造函数可能选择抛出异常的路径 3.用特殊程序用setjmp/longjmp返回控制流 作者尝试了这一办法但是引入未定义行为
作者提议编译器提供两种模式来处理noexcept,真的抛了异常怎么办 1.默认生产模式,他和原要求一样标记noexcept的函数真的抛出异常了就终止,绝不把异常传播异常规范函数之外。 2.测试模式禁用优化,异常传播出去会进行正常的栈回溯,保证和noexcept看到相同的结果。