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

在 catch 中的以下 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?
}
79954 次浏览

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

第一个问题,是的。

但第二点,请参考弗拉德的回答。 您需要仔细设计异常对象来处理复制 ctor。按照约定,基类不能识别其子类,因此很可能会丢失派生类所携带的附加数据。

在这两种情况下,由于是通过引用捕获的,因此实际上是在改变原始异常对象的状态(在下面的示例中,可以将其视为位于 一个神奇的记忆位置,在随后的解除过程中保持有效—— 0x98e7058中)。但是,

  1. 在第一种情况下,由于使用 throw;重新抛出(与 throw err;不同,throw;保留了原始异常对象,并对 0x98e7058的“神奇位置”进行了修改) 将反映对 append ()的调用
  2. 在第二种情况下,因为你显式地抛出一些东西,一个 err收到将被创建然后再抛出(在一个不同的“神奇的位置”0x98e70b0-因为所有编译器都知道 err可能是堆栈上的一个即将被解除的对象,就像 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

参见:

这个问题相当古老,并且有一个与提出这个问题的时间相适应的答案。 然而,我只是想添加一个关于如何进行适当的异常处理的说明,因为 C + + 11,我相信这非常符合您试图实现的追加函数:

使用 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"

C + + 2003标准,是当时的积极标准时,这个问题被问。请注意 C + + 语言有一个标准。

Https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1905.pdf

P. 351,p. 15.4:

When the handler declares a non-constant object, any changes to that object will not affect the temporary object that was initialized by execution of the throw-expression. When the handler declares a reference to a non-constant object, any changes to the referenced object are changes to the temporary object initialized when the throw expression was executed and will have effect should that object be rethrown.

因此,您可以确信,任何正确的 C + + 编译器的 C + + 2003将不会创建任何额外的副本..。

另一种方法是使用如下所示的代码片段并观察对象的确切地址,因为每个对象在 C + + 中都有一个惟一的地址,这意味着对象是相同的。但这只会增强你的自信。

唯一的办法是100% 肯定-看看标准的编程语言。

#include <iostream>


using std::cout;


class A{};


void f1() {
throw A();
}


void f2()
{
try {
f1();
}
catch(A& obj) {
cout << "f1 obj: " << &obj << "\n";
throw;
}
}


void f3()
{
try {
f2();
}
catch(A& obj) {
cout << "f3 obj: " << &obj << "\n";
throw;
}
}


int main()
{
try {
f3();
}
catch(A& obj)
{
cout << "main obj: " << &obj;
}
    

return 0;
}