【问题标题】:Why is the derived exception type lost when catching in C++?为什么在 C++ 中捕获时派生的异常类型会丢失?
【发布时间】:2017-04-06 21:38:22
【问题描述】:

我目前正在使用异常类型,并且在尝试重新抛出捕获的异常时发现了一些奇怪的东西。 从 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 回来?

谢谢

【问题讨论】:

  • derived_exception 派生自 exception,但 derived_exception* 不是派生自 exception*
  • 作为推论,不要通过指针捕获异常 - 通过 const 引用捕获它们。
  • anonymise 是一个错误的名称,因为当true 时,你得到了正确的类型,如果为 false,你得到了基本类型......
  • 投掷/接球系统不会为您提供dynamic_cast 指针。
  • “根据 C++ 规范,我知道 throw 实际上会生成您尝试抛出的对象的副本,因此您最终会分割您捕获的任何残留派生类型信息。” - 我不认为这是真的。

标签: c++ exception inheritance


【解决方案1】:

1。 throw 抛出了什么?

C++14 Standard

5.17 抛出异常

  1. 没有操作数的 throw 表达式会重新抛出当前处理的异常。

15.1 抛出异常

  1. 抛出异常会复制初始化一个临时对象,称为异常对象。该临时对象是一个左值,用于初始化匹配处理程序中声明的变量。

对于语句throw;,会重新抛出当前异常对象derived_exception()

对于语句throw &amp;e;,它将创建一个std::exception *类型的临时对象,实际上等效于throw (std::exception *except_obj = &amp;e);

2。哪个catch 抓住了?

C++14 Standard

15.1 抛出异常

  1. 抛出异常会复制初始化一个临时对象,称为异常对象。 临时值是一个左值,用于初始化匹配处理程序中声明的变量。

15.3 处理异常

  1. 处理程序是匹配类型 E 的异常对象的 if

    • [3.1] 处理程序是 cv T 或 cv T& 类型,并且 E 和 T 是同一类型(忽略顶级 cv 限定符),或者
    • [3.2] 处理程序是 cv T 或 cv T& 类型,并且 T 是 E 的明确公共基类,或者
    • [3.3] 处理程序是 cv T 或 const T& 类型,其中 T 是指针类型,E 是指针类型,可以通过以下两者之一或两者转换为 T
      • 标准指针转换 (4.10) 不涉及到指向私有或受保护或不明确类的指针的转换
      • 资格转换,或
    • [3.4] 处理程序是 cv T 或 const T& 类型,其中 T 是指针或指向成员类型的指针,E 是 std::nullptr_t。
  2. try 块的处理程序按出现的顺序进行尝试。

anonymise == true

throw;被执行,因此重新抛出异常对象derived_exception(),然后在其中选择一个入口点:

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

从标准 15.3-3-3.2 开始,我们知道 derived_exception() 匹配 catch(const derived_exception&amp; e)。因此输出为:

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

anonymise == false:

throw &amp;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],但是标准指针转换限定转换都不支持从基类指针到子类指针的转换,向下-强制转换必须显式完成,例如 usingdynamic_cast&lt;T&gt;()。)

3。在您的问题中

根据 C++ 规范,我知道 throw 实际上会生成您尝试抛出的对象的副本,

没错。

因此,您最终将切分您捕获的任何残留派生类型信息。

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

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

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

我看到了重新抛出指向原始异常的指针的建议,因为实际的原始对象不会删除其派生部分。

通过将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)

【讨论】:

    猜你喜欢
    • 2012-08-08
    • 1970-01-01
    • 1970-01-01
    • 2014-04-14
    • 1970-01-01
    • 2015-10-06
    • 2013-10-26
    • 2011-04-26
    • 1970-01-01
    相关资源
    最近更新 更多