在C++中,“throw”和“throw ex”之间有区别吗?

23

我想问一下这个问题(也可以参考这里),但这次是关于C++的。

C++中throw和throw ex的区别是什么?

try { /*some code here*/}
catch(MyException& ex)
{ throw ex;} //not just throw

并且

try {  /*some code here*/}
catch(MyException& ex)
{ throw;} //not throw ex

这只是在堆栈跟踪中(在C++中,无论如何都不像C#或Java那样是标准)吗?

(如果有任何区别,我使用的是MSVS 2008。)


13
抛出 ex 异常;//公寓外 - fredoverflow
@sehe 对不起,我完全没有理解重点。 - Charles D Pantoga
8个回答

38

throw; 重新抛出它捕获的同一异常对象,而throw ex;则抛出一个新的异常。这并没有区别,除了创建一个新的异常对象的性能原因。如果您有一个异常层次结构,其中有一些其他异常类派生自MyException类,并且在抛出异常时,您使用了throw DerivedClassException;,那么它可以被catch(MyException&)捕获。现在,如果您修改此已捕获的异常对象并使用throw;重新抛出它,则异常对象的类型仍将是DerivedClassException。如果您使用throw Ex;,则会发生对象切片,新抛出的异常将是MyException类型。


3
那取决于 ex 是什么。在这个例子中,它将是同一个对象,因为它是按引用捕获的(这是标准方式)。 - philsquared
2
@Naveen,如果你使用引用捕获,对象切片将不会发生。如果你使用值捕获,切片已经发生了,尽管此时 throw; 可以帮你保存原始异常,因为它会重新抛出原始异常。 - philsquared
3
@Phil: 不是的,请看15.1/3和15.1/6。即使“ex”是一个引用,“throw ex;”不会(必然)抛出引用,而是使用引用初始化临时对象。该临时对象可能会被省略或不被省略。另一方面,“throw;”被指定为重用现有的临时对象。 - Steve Jessop
2
@Phil:我查看了《More Effective C++》第12条,确实存在对象切割问题。我将我的回答恢复为原始回答。 - Naveen
1
@Phil:谢谢,那大概就是我要说的,但我先写了一些代码来证明它 :-)。区别在于指针对象是存在的,但引用对象并不存在。因此,通过throw表达式初始化的临时变量可以是指针,但不能是引用。 - Steve Jessop
显示剩余7条评论

14

[C++ FAQ Lite § 17.9] throw;是什么意思?在哪里会用到它?

你可能会看到类似以下的代码:

class MyException {
public:
  ...
  void addInfo(const std::string& info);
  ...
};

void f()
{
  try {
    ...
  }
  catch (MyException& e) {
    e.addInfo("f() failed");
    throw;
  }
}
在这个例子中,语句throw;的意思是“重新抛出当前异常”。在这里,一个函数通过非常量引用捕获了一个异常,修改了异常(添加信息),然后重新抛出了异常。通过在程序的重要函数中添加适当的catch子句,可以使用这种习惯用法来实现简单形式的堆栈跟踪。
另一种重新抛出异常的惯用法是“异常分发器”:
void handleException()
{
  try {
    throw;
  }
  catch (MyException& e) {
    ...code to handle MyException...
  }
  catch (YourException& e) {
    ...code to handle YourException...
  }
}

void f()
{
  try {
    ...something that might throw...
  }
  catch (...) {
    handleException();
  }
}
这个习惯用法允许在多个函数中重复使用单个函数(handleException())来处理异常。
[C++ FAQ Lite § 17.11] 当我抛出这个对象时,它会被复制几次? 取决于情况。可能是“零”。
被抛出的对象必须具有公共可访问的复制构造函数。编译器允许生成复制抛出对象的代码任意次数,包括零次。但是即使编译器从未实际复制抛出的对象,它也必须确保异常类的复制构造函数存在且可访问。 catch(MyException& ex) { throw ex; } 可能会复制 ex,因此可能带来某些问题。 而 catch(MyException& ex) { throw; } 则不会。

当然可以,但是在上面的代码中,我可以选择做其中之一,那么它们有什么区别呢? - Joshua Fox
非常好的答案,我会补充一下评论,我们通过将大块异常捕获移动到自己的函数中看到了代码加速。这种加速是由于通过将catch块放入函数中使函数更小而实现的。 - chollida
我以前从未注意到“异常分发器”这个习惯用法。很有意思。 - Greg D

13

如果您有一个异常层次结构,throw ex可能会切断您的异常,而throw则不会。例如:

#include <iostream> 
#include <string> 

using namespace std; 

struct base 
{ 
  virtual string who() {return "base";} 
}; 

struct derived : public base 
{ 
  string who() {return "derived";} 
}; 

int main() { 
  try { 
    try { 
      throw derived(); // throws a 'derived'
    }  
    catch (base& ex)  
    { 
      throw ex; // slices 'derived' object to be a 'base' object
    } 
  } 
  catch (base& ex) 
  { 
    cout<<ex.who()<<endl; // prints 'base'
  } 
} 

throw ex 更改为只有 throw,就会得到一个输出结果为 derived,这可能是您希望得到的结果。


谢谢。这很令人惊讶。我不认为在通过引用处理对象时会发生切片。 - Joshua Fox

6
您可以在 catch(...) 中使用 throw; 格式(这是唯一的重新抛出异常的方式,如果您使用 catch(...) 捕获异常)。

两者之间没有特别的关系。throw意味着抛出当前处理的异常,而catch(...)意味着捕获任何异常。 - David Thornley
1
我的观点是,如果你使用catch(...)捕获异常,就不能以其他方式重新抛出。在引用捕获异常并且未被修改的示例中,实际上没有真正的区别。 - philsquared
是的,但我真的很想知道 - 在给定示例中的代码中,如果我可以选择其中一个,我应该选择哪个,为什么? - Joshua Fox
如果你只想重新抛出原始异常,请使用 throw; - philsquared

3

2

throw ex会创建另一个副本,不建议使用。只使用throw来抛出当前异常对象。


1
这是正确的,但只有在按值捕获时才是如此 - 不过这也不是推荐的方式。 - philsquared
就像代码示例中所示,我正在使用引用捕获异常,因此这应该不是一个问题。 - Joshua Fox
4
经测试,即使使用引用捕获,也确实会创建对象的副本,并且在此过程中进行切片。简单的“throw”语句会重新抛出异常,而不会进行切片,即使真正的异常类型是“MyException”的派生类。 - UncleBens
@UncleBens - 正如我在其他地方承认的那样,你是正确的。这真是令人惊讶。 - philsquared

2

throw可以抛出非标准的异常类型,这些异常类型可以被catch(...)捕获(例如结构化异常)。


那不是在VC6中记录为“意外副作用,在VC7中修复”吗? - peterchen
是的,那是一个好观点。不过,在这种情况下,我知道异常类型(MyException)。那么,有什么区别吗? - Joshua Fox
@peterchen,希望不是这样。这对于捕获SEH异常、清理并重新抛出它们非常有用。 - Joshua

1
此外,由于它有时会引起混淆,在异常处理上下文之外使用裸的throw;将中止程序。

任何未被捕获的抛出异常都将调用 unexpected(),除非另有规定(名称来自内存)。在这种情况下,裸 throwthrow something 有什么区别? - David Thornley
@David,我认为Adam的意思是“throw;”而处理程序并没有被激活。也就是说,在任何动态异常处理程序范围之外,它将调用std::terminate(它不需要发生在异常处理程序的大括号内,但这样的处理程序必须已经在当前执行序列中进入并且没有离开)。 - Johannes Schaub - litb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接