重新抛出原始异常时的C ++异常问题


117

捕获中的以下append()是否会导致重新抛出的异常,以查看被调用append()的效果?

try {
  mayThrowMyErr();
} catch (myErr &err) {
  err.append("Add to my message here");
  throw; // Does the rethrow exception reflect the call to append()?
}

同样,如果我用这种方式重写它,如果实际异常是由myErr派生的,会发生位切片吗?

try {
  mayThrowObjectDerivedFromMyErr();
} catch (myErr &err) {
  err.append("Add to my message's base class here");
  throw err; // Do I lose the derived class exception and only get myErr?
}

Answers:


150

在这两种情况下,由于都是通过引用捕获的,所以您实际上是在更改原始异常对象的状态(可以将其视为位于神奇的内存位置,该位置在随后的展开过程中将保持有效 - 0x98e7058在下面的示例中)。然而,

  1. 在第一种情况下,由于您重新抛出throw;(与,不同的是throw err;,它保留了原始的异常对象,并通过您的修改将其保留在处的“神奇位置” 0x98e7058反映对append()的调用
  2. 在第二种情况下,因为你扔东西明确,一个副本err将被创建,然后重新抛出(在不同的“神奇的位置” 0x98e70b0-因为所有的编译器知道err可能是在堆栈上的对象大约是unwinded,就像e是在0xbfbce430,而不是在的“魔术位置” 0x98e7058,因此您将在基类实例的复制构造过程中丢失特定派生类的数据

一个简单的程序来说明正在发生的事情:

#include <stdio.h>

struct MyErr {
  MyErr() {
    printf("  Base default constructor, this=%p\n", this);
  }
  MyErr(const MyErr& other) {
    printf("  Base copy-constructor, this=%p from that=%p\n", this, &other);
  }
  virtual ~MyErr() {
    printf("  Base destructor, this=%p\n", this);
  }
};

struct MyErrDerived : public MyErr {
  MyErrDerived() {
    printf("  Derived default constructor, this=%p\n", this);
  }
  MyErrDerived(const MyErrDerived& other) {
    printf("  Derived copy-constructor, this=%p from that=%p\n", this, &other);
  }
  virtual ~MyErrDerived() {
    printf("  Derived destructor, this=%p\n", this);
  }
};

int main() {
  try {
    try {
      MyErrDerived e;
      throw e;
    } catch (MyErr& err) {
      printf("A Inner catch, &err=%p\n", &err);
      throw;
    }
  } catch (MyErr& err) {
    printf("A Outer catch, &err=%p\n", &err);
  }
  printf("---\n");
  try {
    try {
      MyErrDerived e;
      throw e;
    } catch (MyErr& err) {
      printf("B Inner catch, &err=%p\n", &err);
      throw err;
    }
  } catch (MyErr& err) {
    printf("B Outer catch, &err=%p\n", &err);
  }
  return 0;
}

结果:

  Base default constructor, this=0xbfbce430
  Derived default constructor, this=0xbfbce430
  Base default constructor, this=0x98e7058
  Derived copy-constructor, this=0x98e7058 from that=0xbfbce430
  Derived destructor, this=0xbfbce430
  Base destructor, this=0xbfbce430
A Inner catch, &err=0x98e7058
A Outer catch, &err=0x98e7058
  Derived destructor, this=0x98e7058
  Base destructor, this=0x98e7058
---
  Base default constructor, this=0xbfbce430
  Derived default constructor, this=0xbfbce430
  Base default constructor, this=0x98e7058
  Derived copy-constructor, this=0x98e7058 from that=0xbfbce430
  Derived destructor, this=0xbfbce430
  Base destructor, this=0xbfbce430
B Inner catch, &err=0x98e7058
  Base copy-constructor, this=0x98e70b0 from that=0x98e7058
  Derived destructor, this=0x98e7058
  Base destructor, this=0x98e7058
B Outer catch, &err=0x98e70b0
  Base destructor, this=0x98e70b0

另请参阅:


24

这个问题比较老,并且有一个与提出问题的时间相对应的答案。但是,自C ++ 11起,我只想添加有关如何进行适当的异常处理的注释,我相信这与您试图通过append函数实现的目标非常吻合:

使用std::nested_exceptionstd::throw_with_nested

此处此处在StackOverflow上进行描述,如何通过简单地编写适当的异常处理程序(该异常处理程序将重新抛出嵌套的异常)来获得对代码内异常的追溯,而无需调试程序或繁琐的日志记录。

由于您可以使用任何派生的异常类执行此操作,因此可以向此类回溯中添加很多信息!您也可以在GitHub上查看我的MWE,回溯显示如下:

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"

8

是的,重新抛出将重新抛出您已被引用修改的原始异常对象。您还可以捕获基类引用,对其进行修改,但仍然可以通过抛出原始派生异常类型throw;


1

对于第一个问题,是的。

但第二,请参考弗拉德答案。您将需要仔细设计异常对象以处理复制ctor。按照惯例,基类无法识别其子类,因此您很可能会丢失派生类携带的其他数据。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.