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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2360597/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 23:12:52  来源:igfitidea点击:

C++ Exceptions questions on rethrow of original exception

c++exceptionrethrow

提问by WilliamKF

Will the following append() in the catch cause the rethrown exception to see the effect of append() being called?

catch中下面的append()会不会导致rethrown异常来看看调用append()的效果?

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

Similarly, if I rewrite it this way, will bit slicing occur if the actual exception is derived by myErr?

同样,如果我这样重写,如果实际异常是由 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?
}

回答by vladr

In both cases, since you catch by reference, you are effectively altering the state of the original exception object (which you can think of as residing in a magical memory location which will stay valid during the subsequent unwinding-- 0x98e7058in the example below). However,

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

  1. In the first case, since you rethrow with throw;(which, unlike throw err;, preserves the original exception object, with your modifications, in said "magical location" at 0x98e7058) willreflect the call to append()
  2. In the second case, since you throw something explicitly, a copyof errwill be created then thrown anew (at a different "magical location" 0x98e70b0-- because for all the compiler knows errcould be an object on the stack about to be unwinded, like ewas at 0xbfbce430, not in the "magical location" at 0x98e7058), so you will lose derived-class-specific dataduring the copy-construction of a base class instance.
  1. 在第一种情况下,由于您重新抛出 with throw;(与 不同throw err;,保留原始异常对象,通过您的修改,在上述“神奇位置” at 0x98e7058反映对 append() 的调用
  2. 在第二种情况下,由于您显式抛出了一些东西,因此将创建一个副本err然后重新抛出(在不同的“神奇位置” 0x98e70b0——因为编译器知道err可能是堆栈上一个即将展开的对象,就像e是at 0xbfbce430,而不是在 ) 的“神奇位置” 0x98e7058,因此在基类实例的复制构造过程中,您将丢失派生类特定的数据

Simple program to illustrate what's happening:

简单的程序来说明发生了什么:

#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;
}

Result:

结果:

  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

Also see:

另见:

回答by GPMueller

This question is rather old and has an answer appropriate to the time it was asked. However, I just want to add a note on how to do proper exception handling since C++11and I believe this corresponds very well to what you were trying to achieve with your append function:

这个问题相当古老,并且有一个适合它被问到的时间的答案。但是,我只想添加一个关于如何从C++11开始正确处理异常的注释,我相信这与您尝试使用 append 函数实现的目标非常吻合:

Use std::nested_exceptionand std::throw_with_nested

使用std::nested_exceptionstd::throw_with_nested

It is described on StackOverflow hereand here, how you can get a backtrace on your exceptionsinside your code without need for a debugger or cumbersome logging, by simply writing a proper exception handler which will rethrow nested exceptions.

此处此处的StackOverflow 上进行描述,您可以通过简单地编写一个适当的异常处理程序来重新抛出嵌套异常,从而在不需要调试器或繁琐的日志记录的情况下获得代码中异常的回溯

Since you can do this with any derived exception class, you can add a lot of information to such a backtrace! You may also take a look at my MWE on GitHub, where a backtrace would look something like this:

由于您可以使用任何派生的异常类来执行此操作,因此您可以向此类回溯添加大量信息!您也可以在 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"

回答by Tronic

Yes, rethrowing rethrows the original exception object, which you have modified by a reference. You can also catch a base class reference, modify by it and still be able to rethrow the original derived exception type by throw;.

是的,重新抛出会重新抛出您通过引用修改的原始异常对象。您还可以捕获基类引用,通过它进行修改,并且仍然能够通过 重新抛出原始派生异常类型throw;

回答by YeenFei

for first question, yes.

第一个问题,是的。

but for second, refer to Vlad answer. you will need to carefully design your exception object to handle copy ctor. by convention, base class doesn't recognize its child so you will most likely lose the additional data carried by derived class.

但其次,请参阅弗拉德的回答。您需要仔细设计您的异常对象来处理复制构造函数。按照惯例,基类不识别其子类,因此您很可能会丢失派生类携带的附加数据。