捕获C++基础异常

5

在我的项目中,我们有一个基本异常用于处理显示错误对话框、日志等。我正在寻找一种处理该异常所有派生类的方法,我认为以下方法可以实现:

try
{
  main_loop();
}
catch (const MyExceptionBase* e)
{
  handle_error(e);
}

每个被抛出的子实例都可以用指向其父实例的指针来表示。但是,当异常被抛出时,它现在是未处理的异常。

为什么会这样呢?C++只抛出引用作为异常吗?这样会使我的catch块无效吗?但是为什么这一开始就编译通过了呢?

我能想到的另一种方法是:

try
{
  main_loop();
}
catch (const ExceptionA& e)
{
  handle_error(e);
}
catch (const ExceptionB& e)
{
  handle_error(e);
}
catch (const ExceptionC& e)
{
  handle_error(e);
}

这种方式看起来有点丑陋。正确的做法是什么?不使用基础异常类吗?或者可以按照我想要的方式解决吗?

附注: handle_error() 函数只是利用基类函数 display_message_box() 并干净地关闭程序。

6个回答

17

只需将两种方法结合使用:使用基类并使用引用。

try
{
  main_loop();
}
catch (const MyExceptionBase& e)
{
  handle_error(e);
}

顺便说一下,C++可以捕获指针,如果你抛出它们的话。但这并不是建议的做法。


我不知道引用也可以指向派生类实例,我会尝试一下的,谢谢! :) - Mizipzor

15

你最好捕获基础引用。但请通过引用而不是指针来执行此操作。例子:

try
{
  main_loop();
}
catch (const MyExceptionBase& e)
{
  handle_error(e);
}

通过指针捕获异常的问题在于它必须是指针抛出的。这意味着它将使用new进行创建。

throw new ExceptionA();

这会留下一个相当大的问题,因为它必须在某个时刻被删除,否则就会出现内存泄漏。谁应该负责删除这个异常?通常很难做到正确处理异常,这就是为什么大多数人通过引用捕获异常。

通常在C++中,你应该...

通过引用捕获异常,通过值抛出异常


1
+1 对于捕获引用,抛出值。这话是谁说的 - Sutter? - Greg Rogers
@Greg,我知道Sutter在他的书中说过这句话,但我不确定他是否是原始来源。 - JaredPar

3

如果你要抛出指针,那么只能使用catch(const sometype* ptr),但在99%的情况下这是不可取的。

catch(const sometype& ptr)之所以有效,是因为r-value会隐式转换为常量引用。


2

我很惊讶你的原始示例不起作用。以下示例能够正常工作(至少在g++中):

class Base { public: virtual ~Base () {} };
class Derived : public Base {};

int main ()
{
  try
  {
    throw new Derived ();
  }
  catch (Base const * b)
  {
    delete b;
  }
}

我非常确定这是要按照15.3/3下的一个小点工作的:

处理程序的类型为cv1 T * cv2,E是可以通过以下方式之一或两者将其转换为处理程序类型的指针类型:

你是通过公共继承从基本异常类型继承吗?基类需要是可访问的基类,否则你的异常就无法被捕获。
和其他答案中提到的一样,用引用抛出/捕获的好处在于你不必担心内存拥有权等问题。但是如果上述示例不起作用,则我不知道为什么引用示例会起作用。

是的,那确实可以工作。我的代码之所以不起作用是因为我使用了“throw ExceptionA;”而不是“new”,所以从未有过指针。但正如jpalecek所述,对父对象的引用显然也可以指向子对象,我之前不知道这一点,但现在似乎可以工作了。 - Mizipzor
一个需要注意的地方是,确保你的异常层次结构的析构函数和拷贝构造函数绝对不会抛出异常。你的对象可能会被复制到“临时异常对象”,而该副本不应该失败。析构函数也是同样的情况。 - Richard Corden
@mizipzor:我怀疑这就是问题所在。如果你抛出一个对象,你需要用一个对象引用来捕获它(但不要那么做),不能使用指向对象的指针来捕获它。使用“catch const MyExceptionBase* e)”时,你只能捕获指针(但也不要那么做)。 - j_random_hacker
+1 Richard Corden。我知道需要非抛出析构函数,但不知道需要非抛出复制构造函数。 - j_random_hacker
“你是否通过公共继承从基本异常类型继承?”这是一个有用的提示。 - Shoonya

1

这应该可以工作:

try {
  main_loop();
} catch (const MyExceptionBase &e) {
  handle_error(e);
}

我假设ExceptionA/B/C都继承自MyExceptionBase...我认为那应该可以正常工作。

PS:您可能还想考虑让MyExceptionBase也继承自std::exception。


从std::exception继承是否有额外的好处? - Mizipzor
不算太多,但未捕获的异常可能会有更有意义的消息(因为许多终止处理程序输出what()成员)。 - Evan Teran

0

正如其他人提到的,您应该捕获基类异常的引用。

至于为什么它首先可以编译,与Java不同,没有任何限制可以抛出或捕获哪些类型。因此,您可以为任何类型放入catch块,即使从未抛出,您的编译器也会愉快地编译它。由于您可以通过指针抛出,因此也可以通过指针捕获。


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