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

发布于 2024-08-23 14:34:21 字数 513 浏览 8 评论 0原文

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?
}

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

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?

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?
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

ゝ杯具 2024-08-30 14:34:21

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

  1. 在第一种情况下,由于您使用 throw; 重新抛出(与 throw err; 不同,它通过修改保留原始异常对象,在所述“神奇位置” " at 0x98e7058) 反映对append()的调用
  2. 在第二种情况下,由于您显式抛出某些内容,因此副本< /strong> 的 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

另请参阅:

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 -- 0x98e7058 in the example below). However,

  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) will reflect the call to append()
  2. In the second case, since you throw something explicitly, a copy of err will be created then thrown anew (at a different "magical location" 0x98e70b0 -- because for all the compiler knows err could be an object on the stack about to be unwinded, like e was at 0xbfbce430, not in the "magical location" at 0x98e7058), so you will lose derived-class-specific data during the copy-construction of a base class instance.

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:

心碎的声音 2024-08-30 14:34:21

这个问题相当古老,并且有适合其提出时间的答案。
但是,我只是想添加一个注释,说明自 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"

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++11 and I believe this corresponds very well to what you were trying to achieve with your append function:

Use std::nested_exception and std::throw_with_nested

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

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:

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"
尴尬癌患者 2024-08-30 14:34:21

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

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;.

你穿错了嫁妆 2024-08-30 14:34:21

当提出这个问题时,C++2003 标准是当时有效的标准。请注意,C++ 语言有一个标准。

https://www.open-std。 org/JTC1/SC22/WG21/docs/papers/2005/n1905.pdf

p.351, p.15.4:

当处理程序声明一个非常量对象时,对该对象的任何更改都不会影响通过执行 throw 表达式初始化的临时对象。当处理程序声明对非常量对象的引用时,对引用对象的任何更改都是对执行 throw 表达式时初始化的临时对象的更改,并且如果重新抛出该对象,该临时对象将生效。

确保 C++2003 的任何正确的 C++ 编译器都不会创建任何额外的副本...

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

100% 确定的唯一方法是查看 Standard for Programming Language。

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

The C++2003 Standart, was at the time the active standard when that question was asked. Please be aware that C++ Language has a standard.

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.

So you can be sure that any proper C++ compiler of C++2003 will not create any extra copies...

Another way you can play with code snippets like presented below and observe the exact address of the object, and as each object has a unique address in C++, that implies that the object is the same. But it only enhances your confidence.

The only way to be 100% sure - look into Standart for Programming Language.

#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;
}
歌枕肩 2024-08-30 14:34:21

对于第一个问题,是的。

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文