为什么在使用C ++时会导致派生的异常类型丢失?

时间:2017-04-06 21:38:23

标签: 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 个答案:

答案 0 :(得分:2)

1。 throw会抛出什么?

  

C++14 Standard

     

5.17抛出异常

     
      
  1. 没有操作数的throw-expression重新抛出当前处理的异常。
  2.         

    15.1抛出异常

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

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

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

2。哪个catch捕获?

  

C++14 Standard

     

15.1抛出异常

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

    15.3处理例外

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

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

    4.   

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。在你的问题

  

从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)