voi*_*ter 472 c++ exception-handling exception noexcept c++11
该noexcept关键字可以适当地应用于许多功能签名,但我不能确定何时我应该考虑在实践中使用它.根据我到目前为止所读到的内容,最后一分钟的添加noexcept似乎解决了移动构造函数抛出时出现的一些重要问题.但是,我仍然无法对一些实际问题提供令人满意的答案,这些问题使我首先要了解更多信息noexcept.
我知道永远不会抛出许多函数的例子,但编译器无法自行确定.noexcept在所有这些情况下我应该附加到函数声明吗?
不得不考虑我是否需要noexcept在每个函数声明之后追加,这将大大降低程序员的工作效率(坦率地说,这将是一个痛苦的屁股).对于哪些情况,我应该更加小心使用noexcept,以及在哪些情况下我可以使用暗示noexcept(false)?
我何时才能真实地期望在使用后观察到性能提升noexcept?特别是,给出一个代码示例,C++编译器在添加之后能够生成更好的机器代码noexcept.
就个人而言,我关心的是noexcept因为编译器提供了更大的自由来安全地应用某些类型的优化.现代编译器是否noexcept以这种方式利用?如果没有,我可以期待他们中的一些人在不久的将来这样做吗?
Pub*_*bby 171
我认为现在提供"最佳实践"答案为时尚早,因为在实践中没有足够的时间来使用它.如果在它们出现之后立即询问了抛出说明符,那么答案将与现在非常不同.
在
noexcept每个函数声明之后我必须考虑是否需要追加将大大降低程序员的工作效率(坦率地说,这将是一种痛苦).
好吧,然后使用它,显然该功能永远不会抛出.
我何时才能真实地期望在使用后观察到性能提升
noexcept?[...]就个人而言,我关心的是noexcept因为提高了编译器安全地应用某些优化的自由度.
似乎最大的优化收益来自用户优化,而不是编译器优化,因为它可能会检查noexcept和重载.大多数编译器遵循一个无惩罚 - 如果你不抛出异常处理方法,所以我怀疑它会在代码的机器代码级别上发生很大变化(或任何事情),尽管可能通过删除处理来减少二进制大小码.
使用noexcept在大4(构造函数,赋值,析构函数不是因为他们已经noexcept)很可能会造成最好的改进如noexcept支票模板代码中"普通",如性病容器.例如,std除非标记std::vector(否则编译器可以推断),否则不会使用类的移动.
Mat*_* M. 123
我不断重复这些日子:首先是语义学.
添加noexcept,noexcept(true)并且noexcept(false)首先是关于语义.它只是顺便提出了一些可能的优化.
作为程序员阅读代码,其存在noexcept类似于const:它有助于我更好地理解可能发生或可能不发生的事情.因此,花一些时间思考你是否知道函数是否会抛出是值得的.对于提醒,可以抛出任何类型的动态内存分配.
好的,现在进行可能的优化.
最明显的优化实际上是在库中执行的.C++ 11提供了许多特性,允许知道函数是否存在noexcept,并且标准库实现本身将使用这些特征来支持noexcept对它们操作的用户定义对象的操作(如果可能).如移动语义.
编译器可能只会从异常处理数据中刮掉一些胖(可能),因为它必须考虑到你可能已经撒谎的事实.如果标记的函数noexcept抛出,则std::terminate调用.
选择这些语义有两个原因:
noexcept即使依赖关系不使用它也会立即受益(向后兼容性)noexcept何时调用理论上可能抛出但不期望给定参数的函数Ter*_*fey 74
这实际上确实对编译器中的优化器产生了(潜在的)巨大差异.编译器实际上已经通过函数定义之后的空throw()语句以及适当扩展多年来具有此功能.我可以向您保证,现代编译器确实利用这些知识生成了更好的代码.
几乎编译器中的每个优化都使用称为函数"流程图"的东西来推断合法的内容.流程图由通常称为函数的"块"(具有单个入口和单个出口的代码区域)和块之间的边缘组成,以指示流可以跳转到的位置.Noexcept改变了流程图.
你问了一个具体的例子.考虑以下代码:
void foo(int x) {
try {
bar();
x = 5;
// Other stuff which doesn't modify x, but might throw
} catch(...) {
// Don't modify x
}
baz(x); // Or other statement using x
}
Run Code Online (Sandbox Code Playgroud)
如果bar标记了此函数的流程图是不同的noexcept(无法执行以bar在catch语句的结尾和catch语句之间跳转).当标记为时noexcept,编译器确定在baz函数期间x的值为5 - x = 5块被称为"支配"baz(x)块而没有边缘bar()到catch语句.然后,它可以执行称为"常量传播"的操作,以生成更高效的代码.如果baz是内联的,那么使用x的语句也可能包含常量,然后可以将运行时评估转换为编译时评估等.
无论如何,简短的回答:noexcept让编译器生成更紧密的流程图,流程图用于推断各种常见的编译器优化.对于编译器,这种性质的用户注释非常棒.编译器会尝试计算出这些东西,但它通常不能(有问题的函数可能在编译器不可见的另一个目标文件中或者传递上使用某些不可见的函数),或者当它确实存在时你甚至不知道可能抛出的普通异常,因此它不能将其隐式标记为noexcept(例如,分配内存可能会抛出bad_alloc).
And*_*zej 51
noexcept可以显着提高某些操作的性能.这不是在编译器生成机器代码的情况下发生的,而是通过选择最有效的算法:如其他人提到的那样,您可以使用函数进行选择std::move_if_noexcept.例如,std::vector(例如,当我们打电话时reserve)的增长必须提供强有力的例外安全保证.如果它知道T移动构造函数没有抛出,它就可以移动每个元素.否则它必须复制所有Ts.这已经详细描述了这个职位.
Nic*_*las 30
除了在使用后观察性能改善之外我什么时候可以实际
noexcept?特别是,给出一个代码示例,在添加noexcept之后,C++编译器能够生成更好的机器代码.
嗯,永远不?永远不是时候?决不.
noexcept用于编译器性能优化的方式与编译器性能优化的方式相同const.那就是,几乎从来没有.
noexcept主要用于允许"你"在编译时检测函数是否可以抛出异常.请记住:大多数编译器不会为异常发出特殊代码,除非它实际上抛出了某些内容.所以noexcept不是给编译器提供关于如何优化函数的提示,而是提供有关如何使用函数的提示.
类似的模板move_if_noexcept将检测是否定义了移动构造函数,如果不是,noexcept则将返回const&而不是&&类型的类型.如果这样做是非常安全的话,这是一种说法.
在一般情况下,你应该使用noexcept当你认为它实际上是有用的这样做.如果is_nothrow_constructible该类型为真,则某些代码将采用不同的路径.如果您正在使用可以执行此操作的代码,那么请随意使用noexcept适当的构造函数.
简而言之:将它用于移动构造函数和类似构造,但不要觉得你必须坚持下去.
Phi*_*ßen 20
- 我知道永远不会抛出许多函数的例子,但编译器无法自行确定.在所有这些情况下,我应该在函数声明中附加noexcept吗?
noexcept很棘手,因为它是函数接口的一部分.特别是,如果您正在编写库,则客户端代码可能取决于noexcept属性.稍后更改它可能很困难,因为您可能会破坏现有代码.当您实现仅由应用程序使用的代码时,这可能不那么令人担忧.
如果你有一个不能抛出的功能,问问自己是否会留下noexcept或者是否会限制未来的实施?例如,您可能希望通过抛出异常(例如,用于单元测试)来引入非法参数的错误检查,或者您可能依赖于可能更改其异常规范的其他库代码.在这种情况下,保守和省略更安全noexcept.
另一方面,如果您确信该函数永远不会抛出,并且它是规范的一部分是正确的,您应该声明它noexcept.但是,请记住,noexcept如果您的实现发生更改,编译器将无法检测到违规.
- 在哪些情况下我应该更加小心使用noexcept,在哪些情况下我可以使用隐含的noexcept(false)?
您应该专注于四类功能,因为它们可能会产生最大的影响:
noexcept(true)除非你制作它们noexcept(false))这些函数通常应该是noexcept,并且库实现很可能可以使用该noexcept属性.例如,std::vector可以使用非投掷移动操作而不会牺牲强大的异常保证.否则,它将不得不回归复制元素(就像在C++ 98中那样).
这种优化是在算法级别上进行的,并不依赖于编译器优化.它可能会产生重大影响,特别是如果要复制的元素很昂贵.
- 在使用noexcept后,我何时能够真实地期望观察到性能提升?特别是,给出一个代码示例,在添加noexcept之后,C++编译器能够生成更好的机器代码.
noexcept无异常规范的优点还是throw()标准允许编译器在堆栈展开时更自由.即使在这种throw()情况下,编译器也必须完全展开堆栈(并且它必须以与对象结构完全相反的顺序执行).
noexcept另一方面,在这种情况下,不需要这样做.没有要求必须解开堆栈(但仍允许编译器执行此操作).这种自由允许进一步的代码优化,因为它降低了始终能够展开堆栈的开销.
关于noexcept,堆栈展开和性能的相关问题涉及到需要堆栈展开时的开销的更多细节.
我还推荐Scott Meyers的书"Effective Modern C++","Item 14:如果它们不会发出异常,则声明函数noexcept"以供进一步阅读.
Sau*_*ahu 19
在Bjarne的话:
在终止是可接受的响应的情况下,未捕获的异常将实现这一点,因为它变成了terminate()的调用(§13.5.2.5).此外,
noexcept说明符(第13.5.1.1节)可以使该愿望明确.成功的容错系统是多级的.每个级别都可以处理尽可能多的错误而不会过度扭曲,并将其余级别留给更高级别.例外支持该观点.此外,
terminate()如果异常处理机制本身已损坏或未完全使用,则通过提供转义来支持此视图,从而使异常未被捕获.同样,noexcept为尝试恢复似乎不可行的错误提供了简单的转义.Run Code Online (Sandbox Code Playgroud)double compute(double x) noexcept; { string s = "Courtney and Anya"; vector<double> tmp(10); // ... }向量构造函数可能无法为其十个双精度获取内存并抛出一个
std::bad_alloc.在这种情况下,程序终止.它通过调用std::terminate()(第30.4.1.3节)无条件终止.它不会调用析构函数来调用函数.它是实现定义的,是否调用了throw和noexcept(例如,对于compute()中的s)之间的范围的析构函数 .程序即将终止,所以我们不应该依赖任何对象.通过添加noexcept说明符,我们指出我们的代码不是为了应对throw而编写的.
Rae*_*ald 15
我知道永远不会抛出许多函数的例子,但编译器无法自行确定.在所有这些情况下,我应该在函数声明中附加noexcept吗?
当你说"我知道[他们]永远不会抛出"时,你的意思是通过检查函数的实现,你知道该函数不会抛出.我认为这种方法是内在的.
最好考虑函数是否可以抛出异常以成为函数设计的一部分:与参数列表一样重要,以及方法是否是mutator(... const).声明"此函数永远不会抛出异常"是对实现的约束.省略它并不意味着函数可能抛出异常; 这意味着该函数的当前版本和所有未来版本可能会抛出异常.这是一个使实施更难的约束.但是有些方法必须具有实际有用的约束条件; 最重要的是,它们可以从析构函数中调用,也可以在提供强大异常保证的方法中实现"回滚"代码.
| 归档时间: |
|
| 查看次数: |
86731 次 |
| 最近记录: |