今天我了解到swap
不允许在C ++中抛出异常。
我也知道以下内容也不能抛出异常:
还有其他人吗?
或者,是否有某种列表可以提及可能不会丢弃的所有内容?
(显然,比标准本身更简洁。)
答案 0 :(得分:16)
不能和不应该之间存在很大差异。原始类型的操作不能抛出许多函数和成员函数,包括标准库和/或许多其他库中的许多操作。
现在不应该,你可以包含析构函数和交换。根据你实现它们的方式,它们实际上可以抛出,但你应该避免使用抛出的析构函数,而在swap
的情况下,提供一个无抛出保证的交换操作是实现强大的最简单的方法您可以复制,在副本上执行操作,然后与原始文件交换,从而保证您的班级有例外保证。
但请注意语言允许两个析构函数和swap
抛出。 swap
可以抛出,在最简单的情况下,如果你不重载它,那么std::swap
执行一个复制构造,一个赋值和一个破坏,三个操作都可以抛出一个例外(取决于您的类型)。
在C ++ 11中,析构函数的规则发生了变化,这意味着没有异常规范的析构函数具有隐式noexcept
规范,这反过来意味着如果它抛出异常,运行时将调用{{1}但是,您可以将异常规范更改为terminate
,然后析构函数也可以抛出。
在一天结束时,如果不了解代码库,则无法提供异常保证,因为C ++中的每个函数都可以抛出。
答案 1 :(得分:8)
所以这并不能完美地回答你的问题 - 我从自己的好奇心中搜索了一下 - 但我相信 nothrow保证的功能/运营商大多来自任何C ++中提供的C风格函数以及一些任意简单的函数可以提供这样的保证。一般来说,C ++程序并不期望提供这种保证(When should std::nothrow be used?),而且如果这样的保证在代码中为您提供经常使用异常的任何有用的东西,那么这一点并不清楚。我找不到一个全面的所有C ++函数列表,这些函数都是非函数函数(如果我错过了一个标准的命令,请纠正我)除了交换,析构函数和原始操作的列表之外。对于未在库中完全定义的函数来说,要求用户实现nothrows函数似乎相当罕见。
所以也许为了找到问题的根源,你应该主要假设任何东西都可以抛出C ++,当你发现一些绝对不能抛出异常的东西时,可以把它作为一种简化。编写异常安全代码就像编写无错代码一样 - 它比听起来更难,老实说通常不值得努力。此外,异常不安全代码和强大的非运行函数之间存在许多级别。请参阅以下关于将异常安全代码编写为这些点的验证的精彩答案:Do you (really) write exception safe code?。有关异常安全的更多信息,请访问提升网站http://www.boost.org/community/exception_safety.html。
对于代码开发,我听过教授和编码专家关于应该和不应该抛出异常以及这些代码应该提供什么保证的不同意见。但是一个相当一致的断言是,很容易抛出异常的代码应该非常清楚地记录下来,或者在函数定义中指出抛出的能力(并不总是仅适用于C ++)。可能抛出异常的函数比从不抛出的函数更常见,并且知道可能发生的异常非常重要。但是保证将一个输入除以另一个输入的函数永远不会抛出0分频异常可能是非常不必要/不需要的。因此,对于安全的代码执行,nothrow可以让人放心,但不是必需的,或者总是有用。
回应对原始问题的评论:
人们有时会说当抛出容器或一般情况下抛出构造函数是邪恶的,并且应始终使用两步初始化和is_valid检查。但是,如果构造函数失败,它通常是不可修复的或处于唯一错误的状态,否则构造函数将首先解决问题。检查对象是否有效就像在初始化代码周围放置try catch块一样困难,因为对象你知道有很多机会抛出异常。哪个是正确的?通常以代码库的其余部分或您的个人偏好中使用的为准。我更喜欢基于异常的代码,因为它让我感觉更灵活,没有大量的行李代码检查每个对象的有效性(其他人可能不同意)。
这会给您留下原始问题以及评论中列出的扩展名?好吧,从提供的资源和我自己的经验来看,担心在C ++的“异常安全”视角下不使用函数通常是处理代码开发的错误方法。相反,请记住您知道的函数可能合理地抛出异常并适当地处理这些情况。这通常涉及IO操作,您无法完全控制触发异常的内容。如果你得到一个你从未想过或不想到的异常,那么你的逻辑中有一个错误(或者你对函数使用的假设),你需要修改源代码以适应。试图保证代码是非平凡的(有时甚至是那时)就像说服务器永远不会崩溃 - 它可能非常稳定,但你可能不会百分百肯定。
答案 2 :(得分:4)
如果您想要详尽解答此问题,请转到http://exceptionsafecode.com/并观看仅涵盖C ++ 03的85分钟视频或涵盖两者的三小时(分两部分)视频C ++ 03和C ++ 11。
在编写Exception-Safe代码时,我们假设所有函数都抛出,除非我们知道不同。
简而言之,
*)基本类型(包括数组和指针)可以与不涉及用户定义的运算符的运算一起分配和使用(例如,仅使用基本整数和浮点值的数学运算)。请注意,除以零(或其结果未在数学上定义的任何表达式)是未定义的行为,可能会或可能不会根据实现而抛出。
*)析构函数:析构函数发出异常并没有概念上的错误,标准也没有禁止它们。但是,良好的编码指南通常会禁止它们,因为语言不能很好地支持这种情况。 (例如,如果STL容器中的对象的析构函数抛出,则行为未定义。)
*)使用swap()是一种提供强异常保证的重要技术,但前提是swap()是非抛出的。通常,我们不能假设swap()是非抛出的,但视频介绍了如何在C ++ 03和C ++ 11中为用户定义类型创建非抛出交换。
*)C ++ 11引入了移动语义和移动操作。在C ++ 11中,swap()是使用移动语义实现的,移动操作的情况类似于swap()的情况。我们不能假设移动操作不会抛出,但我们通常可以为我们创建的用户定义类型创建非抛掷移动操作(并且它们是为标准库类型提供的)。如果我们在C ++ 11中提供非投掷移动操作,我们可以免费获得非投掷swap(),但我们可以选择以任何方式实现我们自己的swap()以达到性能目的。同样,这在视频中有详细介绍。
*)C ++ 11引入了noexcept运算符和函数装饰器。 (Classic C ++中的“throw()”规范现已弃用。)它还提供函数内省,以便根据是否存在非抛出操作来编写代码以处理不同情况。
除了视频外,exceptionsafecode.com网站还提供了有关C ++ 11需要更新的例外的书籍和文章的参考书目。