我目前正在玩异常类型,并且在尝试重新捕获异常时我发现了一些奇怪的东西。
从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
回来?
由于
答案 0 :(得分:2)
throw
会抛出什么?5.17抛出异常
- 没有操作数的throw-expression重新抛出当前处理的异常。
醇>15.1抛出异常
- 抛出异常copy-initialize一个临时对象,称为异常对象。临时是一个左值,用于初始化匹配处理程序中声明的变量。
醇>
对于语句throw;
,它将重新抛出当前异常对象derived_exception()
。
对于语句throw &e;
,它将创建一个std::exception *
类型的临时对象,实际上等同于throw (std::exception *except_obj = &e);
。
catch
捕获?15.1抛出异常
- 抛出异常复制 - 初始化一个称为异常对象的临时对象。 临时是左值,用于初始化匹配处理程序中声明的变量。
醇>15.3处理例外
处理程序是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。
- 醇>
尝试块的处理程序按照出现的顺序进行尝试。
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>()
。)
从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)