在C++中通过指针捕获异常

54

我发现有三种方法可以捕获异常,它们有什么不同?

1)值捕获;

2)引用捕获;

3)指针捕获;

我只知道值捕获会调用对象的两个副本,引用捕获只会调用一个。那么指针捕获呢?何时使用指针捕获?除了抛出一个对象之外,我能像这样抛出一个指向对象的指针吗?

class A {}

void f() {

  A *p = new A();
        throw p;


}

7
你不能通过指针捕获异常。你可以捕获一个异常,它恰好是一个指针。问题在于A和A是两种完全不同的类型。如果你抛出一个指向A的指针,那么你只能通过值或引用来捕获。但你实际上是通过值或引用捕获了A而不是A。 - Martin York
1
另外,由于C++编译器允许(尽管不强制)省略两个复制过程,对于第一个复制(然后抛出异常),如果不能省略复制,则必须移动对象而不是复制。 - Arthur P. Golubev
5个回答

89

推荐的方法是按值抛出异常,按引用捕获异常

你的示例代码抛出指针是个坏主意,因为你必须在捕获时管理内存。

如果你真的想抛出指针,请使用智能指针,例如shared_ptr

无论如何,Herb Sutter和Alexei Alexandrescu在他们的C++编程标准书中很好地解释了这一点,我进行了概述。

请查看C++编程标准:按值抛出异常,按引用捕获异常


16
如果你之所以要抛出异常是因为内存不足,那么试图分配一个新对象来进行抛出是没有帮助的。 - Nathan Osman
3
根据A是否可以分配,你可能会抛出指向A的指针,或抛出std::bad_alloc异常。因此,至少要抛出某些东西... - Steve Jessop
我看到过这样的代码:const std::runtime_error err; throw err;。我是否正确地认为,实际上不会通过引用捕获它(从const std::runtime_error&std::runtime_error&没有转换)?因此,这将被运行时捕获,并可能导致程序崩溃。我是对的吗? - the swine
@SteveJessop,这是一个非常明智的观察。我一直担心在std::exception构造函数中复制带有异常描述的std::string。但是如果复制失败,我猜测会抛出一个无原因的std::bad_alloc异常。或者可能会导致无限递归 :)。想得很周到。 - the swine
1
@theswine 不是的。当进行抛出临时对象操作时,[N3337, 15.1$3] 该内存以未指定的方式分配,但不是由 operator new 运算符实现的。[N3337, 15.1$4] 其中 throw 表达式中包含的任何内容都用于初始化该临时对象(通过复制 - 可能已省略 - 或移动)。在您给出的示例中,将创建 std::runtime_error 的临时对象,并通过从 err 复制来进行初始化。然后可以通过 & 和 const& 来捕获它。 - Adam Badura

18

Catch 遵循常规的赋值兼容性规则,也就是说,如果你抛出一个值,你可以将其作为值或引用来 catch,但不能作为指针;如果你抛出一个指针,你只能将其作为指针(或指针的引用)来 catch ......

但抛出指针实际上没有什么意义,它只会带来内存管理方面的麻烦。因此,通常应遵循“按值抛出,按引用 catch”的规则,正如 Gregory 所解释的那样。


如果我表现得天真,我向你道歉,但如果我们通过“引用”来捕获,那么那不会是未定义行为吗?因为在“try”块中创建的对象的作用域被销毁了,对吗?或者情况是这样的,它动态地分配返回对象,然后发送对其的引用吗? - Floatoss
如果我表现得天真,我向你道歉,但是如果我们通过引用来“捕获”,那不会导致未定义行为吗?因为在“try”块中创建的对象的作用域会被销毁,对吗?还是说,它动态分配返回对象,然后发送对该对象的引用? - undefined

6

Microsoft的MFC使用指针捕获,但我认为这是为了与try和catch正确实现之前的编译器兼容性;最初他们使用TRY和CATCH宏来模拟它。每个异常都派生自CException,它有一个方法来确定对象是否需要被删除。

我不建议在任何现代异常设计中使用这种方式。通过引用捕获是正确的方式。


3
尽管可以抛出任何类型的任何对象,但这样做几乎没有什么好处。动态分配主要在对象的生存期不适合自动分配时很有用——也就是说,您希望其生存期独立于正常程序范围。
然而,在异常对象的情况下,这并没有太多意义。异常对象通常只在异常处理程序内部使用,并且显然希望在退出该异常的(最后一个)处理程序时销毁它。
此外,一般希望保持异常处理代码相当简单。例如,如果您试图报告自由存储器/堆已耗尽或损坏,那么尝试从该已耗尽/损坏的自由存储器/堆中分配异常对象通常不会很好地工作...

1

没有一个好的场景可以通过指针捕获/抛出异常。 C++语义允许它,但它并不是非常有用,因为大多数时候你会抛出一个临时异常或字符串对象。

然而,一些库(我相信Boost.Graph就是这样做的)使用throw将返回值从深度递归函数传递回调用者;在这种情况下,返回值可能是一个指针,因此抛出指针是有意义的。


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