在修改后的异常上使用 `throw;`

Posted

技术标签:

【中文标题】在修改后的异常上使用 `throw;`【英文标题】:Using `throw;` on a modified exception 【发布时间】:2015-01-09 02:34:04 【问题描述】:

我有一个函数 foo 可以抛出 bar 异常。

在另一个函数中,我调用foo,但如果抛出bar 异常,我可以添加更多细节。 (我宁愿不将此类信息作为参数传递给foo,因为由于该函数的通用性质,它并不真正属于那里。)

所以我在调用者中这样做:

try 
    foo();
 catch (bar& ex)
    ex.addSomeMoreInformation(...);
    throw;

throw 会重新抛出修改后的异常还是我需要使用throw ex;?后者可能会采用价值副本,所以我宁愿不这样做。 throw 也会接受价值副本吗?我怀疑它不会。

(我知道我可以验证,但我担心会遇到未指定或未定义的构造,所以想确定一下)。

【问题讨论】:

【参考方案1】:

throw(没有异常对象)将重新抛出当前异常。 (必须在 catch 块内,否则会调用 std::terminate)。因为更改了当前异常对象的引用,所以不需要显式抛出该对象并重新抛出修改后的异常,不会创建新的临时对象。

【讨论】:

对,他知道。你没有回答问题。 @LightnessRacesinOrbit you don't need to explicilty throw the object 是我相信的答案。在此之前是原因。我猜那是你不喜欢和反对的东西。 不,伙计,这不是的答案。当异常对象被修改时,OP 想要知道throw 做了什么。您的回答甚至无法解释这一点。它只是说“它将重新抛出当前的异常”——是的,OP 已经知道了。他正试图获得关于其工作原理的更多细节。没必要这么防御。 @LightnessRacesinOrbit: Ad rem: OP 询问他是否需要throw ex 以抛出修改后的对象这一事实意味着他不确定修改后的异常是否是当前异常或者是否发生了类似于写入时隐藏副本的事情。 OP回答了这个问题。广告“防御性”:对被认为是贬低的评论的一种可以理解的反应。在没有详细说明的情况下,“没有回答问题”的轻快声明是在一个将声誉建立在学科知识和能力之上的社区中损害声誉。 @LightnessRacesinOrbit 我没有骂。我试图展示一个看似合理的想法链,表明 OP 可能对 Gyapti 所做的声明完全不安全。据我了解,如果 OP 确定 ex 指的是原始异常,那么这个问题实际上是没有意义的,因此任何更改都包含在由 throw 重新抛出的异常中,而不是 @987654327 @。 OP提出这个问题,因此imo表明与您认为它所说的完全相反。最好更详细一些,以便找出确切的意思。【参考方案2】:

其实这里的标准是非常精确的。 [except.handle]/17:

当处理程序声明对非常量对象的引用时,any 对引用对象的更改是对临时对象的更改 在执行 throw-expression 时初始化,并将具有 重新抛出该对象的效果

还有[except.throw]/8:

一个没有操作数的 throw-expression 重新抛出当前处理的 例外 (15.3)。

【讨论】:

【参考方案3】:

C++11 §15.1/8:

没有操作数的 throw-expression 会重新抛出当前处理的异常 (15.3)。例外是 用现有的临时重新激活;没有创建新的临时异常对象。

【讨论】:

请用非标准语言添加一行解释。这样我什至不明白答案。修改是否生效? @marczellm "没有创建新的临时异常对象" @anonymous downvoter:请解释您的反对意见,这令人费解。 @marczellm:“没有创建新的临时异常对象”表示没有创建新对象。而是重用当前的。通过引用捕获意味着直接引用该对象,并且不会丢失对其所做的修改。请注意,如果没有明显的效果,关于没有新临时的声明将毫无意义。所以这是理解标准语的线索:对于每一种解释,都要问“它有意义吗?”。标准中的大多数陈述都是有意义的。 @marczellm:话虽如此,要在 C++ 编程方面取得成功,必须始终考虑至少向前迈出一步。 SO 中某人关于标准或某些文档中的某些内容的断言通常是毫无价值的,因为大约一半的 SO 答案在技术上是不正确的(是的,即使是那些由高级代表用户编写的)。除非您可以通过自己的想法来验证它,否则它是不好的。当这种想法只是推理的一个步骤时,就像这里一样,那么自己思考要比被灌输一个假定的解释然后有更多的东西去验证要好得多。【参考方案4】:

在这种情况下,您应该使用 throw 来获得所需的行为...即 throw 会抛出修改后的异常,因为该异常是通过引用捕获的。

让我尝试通过示例来明确区分这些抛出:-

class exception

;

class MyException : public exception

;

void func()

  try
  
    throw MyException();
  
  catch( exception& e )
  
    //do some modification.
    throw;                    //Statement_1
    throw e;                  //Statement_2
   

Statment_1:-

throw 所做的只是重新抛出当前异常,即它不会进行进一步的复制(就像最初抛出异常时所做的那样)。因此,如果您在此处对捕获的异常进行任何更改...它也会出现在调用程序例程中。

语句_2:-

这是抛出最初作为 MyException 捕获的“异常”,即它会再次复制。因此,只需忘记您所做的更改,它甚至不会将 or*ginal 异常传递给调用者。它向调用例程抛出“异常”。

希望我足够清楚(并且正处于 C++ 标准的轨道上)......

【讨论】:

我看不出throw e 会如何神奇地“忘记您所做的更改”。该副本可能不理想,但您仍然在复制异常对象修改后 哦,没错,这是个问题。好的。但是,将执行切片这一事实与说 OP 的修改将丢失是不同的:它们不会。【参考方案5】:

根据this,c++中抛出异常有两种方式:

    throw expression:首先,从表达式复制初始化异常对象(这可能会调用右值表达式的移动构造函数,并且复制/移动可能会受到复制省略) ,然后将控制权转移到具有匹配类型的异常处理程序,该处理程序的复合语句或成员初始化程序列表是最近进入的,并且没有被此执行线程退出。 throw:重新抛出当前处理的异常。放弃当前 catch 块的执行并将控制权传递给下一个匹配的异常处理程序(但不传递给同一个 try 块之后的另一个 catch 子句:它的复合语句被认为已“退出”),重用现有的异常对象:没有新的对象产生。仅当当前正在处理异常时才允许使用此形式(如果以其他方式使用,则调用 std::terminate)。如果在构造函数上使用与 function-try-block 关联的 catch 子句,则必须通过重新抛出退出。

所以为了强调我的回答,throw 在你的情况下应该没问题。

【讨论】:

以上是关于在修改后的异常上使用 `throw;`的主要内容,如果未能解决你的问题,请参考以下文章

异常处理(throw,throws,try,catch,finally)

分类 - 修改后的 Huber 损失:它如何更能容忍异常值?

异常处理

Java中的异常处理:何时抛出异常,何时捕获异常?

throw 和 throws 的区别是什么?

Java异常处理中关键字throws,throw,try,catch,finally分别代表啥意义?在try块中可以抛出异常吗?