c++ - 为什么在 C++ 中捕获时派生异常类型会丢失?

标签 c++ exception inheritance

我目前正在研究异常类型,在尝试重新抛出捕获的异常时我注意到了一些奇怪的事情。 从 C++ 规范中,我知道 throw 实际上会生成您尝试抛出的对象的拷贝,因此您最终将分割您捕获的任何剩余派生类型信息。 为避免这种情况,我看到了重新throw 指向原始异常的指针的建议,因为实际的原始对象将不会删除其派生部分。 但是,我在下面编写的简单示例程序似乎不能那样工作:

#include <exception>
#include <iostream>
#include <typeinfo>

class derived_exception : public std::exception { };

void rethrowException(bool anonymise) {
  try {
    throw derived_exception();
  } catch(const std::exception& e) {
    std::cout << "Caught: " << typeid(e).name() << " (std::exception)" << std::endl;
    if(anonymise) {
      throw;
    } else {
      throw &e;
    }
  }
}

int main() {
  std::cout << "Re-throwing caught exception..." << std::endl;
  try {
    rethrowException(false);
  } catch(const derived_exception* e) {
    std::cout << "Re-caught: " << typeid(e).name() << " (derived_exception)" << std::endl;
  } catch(const std::exception* e) {
    std::cout << "Re-caught: " << typeid(e).name() << " (std::exception)" << std::endl;
  }
    std::cout << std::endl << "Re-throwing anonymous exception..." << std::endl;
  try {
    rethrowException(true);
  } catch(const derived_exception& e) {
    std::cout << "Re-caught: " << typeid(e).name() << " (derived_exception)" << std::endl;
  } catch(const std::exception& e) {
    std::cout << "Re-caught: " << typeid(e).name() << " (std::exception)" << std::endl;
  }
}

./example 的输出:

Re-throwing caught exception...
Caught: 17derived_exception (std::exception)
Re-caught: PKSt9exception (std::exception)

Re-throwing anonymous exception...
Caught: 17derived_exception (std::exception)
Re-caught: 17derived_exception (derived_exception)

您可以成功地重新转换指针并检索派生类型信息,但指针类型仍然是初始切片。 有没有办法在不捕获基地并尝试 dynamic_cast 返回的情况下解决这个问题?

谢谢

最佳答案

1。什么throw throw ?

C++14 Standard

5.17 Throwing an exception

  1. A throw-expression with no operand rethrows the currently handled exception.

15.1 Throwing an exception

  1. Throwing an exception copy-initializes a temporary object, called the exception object. The temporary is an lvalue and is used to initialize the variable declared in the matching handler.

对于声明throw; ,它会重新抛出当前异常对象derived_exception() .

对于声明throw &e; , 它将创建一个 std::exception * 类型的临时对象, 实际上等同于 throw (std::exception *except_obj = &e); .

2。哪个catch捕获?

C++14 Standard

15.1 Throwing an exception

  1. Throwing an exception copy-initializes a temporary object, called the exception object. The temporary is an lvalue and is used to initialize the variable declared in the matching handler.

15.3 Handling an exception

  1. A handler is a match for an exception object of type E if

    • [3.1] The handler is of type cv T or cv T& and E and T are the same type (ignoring the top-level cv-qualifiers), or
    • [3.2] the handler is of type cv T or cv T& and T is an unambiguous public base class of E, or
    • [3.3] the handler is of type cv T or const T& where T is a pointer type and E is a pointer type that can be converted to T by either or both of
      • a standard pointer conversion (4.10) not involving conversions to pointers to private or protected or ambiguous classes
      • a qualification conversion, or
    • [3.4] the handler is of type cv T or const T& where T is a pointer or pointer to member type and E is std::nullptr_t.
  2. The handlers for a try block are tried in order of appearance.

anonymise == true :

throw;被执行,从而重新抛出异常对象derived_exception() , 然后选择一个入口点:

  • catch(const derived_exception& e) ,
  • catch(const std::exception& e) .

从标准 15.3-3-3.2,我们知道 derived_exception()火柴catch(const derived_exception& e) .因此输出是:

Re-caught: 17derived_exception (std::exception)

anonymise == false :

throw &e;被执行,从而创建一个 std::exception * 类型的临时异常对象, 然后选择一个入口点:

  • catch(const derived_exception* e) ,
  • catch(const std::exception* e) .

我们不能选择前者。因为标准 15.3-3 中的四个规则都没有说 std::exception *被认为是 const derived_exception * 的匹配项.

所以,后来的catch被选中。我们看到输出:

Re-caught: PKSt9exception (std::exception)

( 你可能想争论第三条规则[3.3],但是标准指针转换限定转换都不支持从基类指针到子类指针的转换,向下-转换必须显式完成,例如使用 dynamic_cast<T>() 。)

3。在你的问题中

From the C++ specification, I know that throw actually produces a copy of the object you're attempting to throw,

没错。

so you will end up slicing any residual derived-type information that you caught.

如果您在 catch 中使用值类型而不是引用或指针,则正确.一个例子是

try {
    throw derived_exception();
} catch (const std::exception e) {
    ...
}

从标准 15.1-3,我们在这里知道 e将用 derived_exception() 初始化.实际上,这就像执行 e = derived_exception(); .不过,我找不到使用此表单的任何理由。

I have seen suggestions to re-throw a pointer to the original exception as the actual original object will then not have its derived portion removed.

通过替换 typeid(e).name()typeid(*e).name() ,我们可以看到原始对象没有被切片:

catch (const std::exception *e)
{
    std::cout << "Re-caught: " << typeid(*e).name() << " (std::exception)"
              << std::endl;
}
// Re-caught: 17derived_exception (std::exception)

关于c++ - 为什么在 C++ 中捕获时派生异常类型会丢失?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43266188/

相关文章:

c++ - 条件显式模板实例化

macos - Grails GGTS IDE GrailsProcessDiedException : Grails process died

c# - 顶层异常

scala - 案例类中的产品继承

c++ - 继承:包含派生类实例的基类模板容器

c++ - 数据未按应有的方式使用 lambda 存储在 vector 中

c++ - 为什么即使我设置了 THREAD_MODE_BACKGROUND_BEGIN,我的程序还是资源消耗大户?

JAVA实现接口(interface)

c++ - 我的 2D 迷宫解算器无限递归并且出现堆栈溢出 - 为什么?

java - 使用 while 循环处理异常