为什么在 C++ 中捕获时派生异常类型会丢失?
Why is the derived exception type lost when catching in C++?
我目前正在研究异常类型,在尝试重新抛出捕获的异常时我注意到了一些奇怪的事情。
从 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
抛出什么?
5.17 Throwing an exception
- A throw-expression with no operand rethrows the currently handled exception.
15.1 Throwing an exception
- 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
抓住了?
15.1 Throwing an exception
- 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
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.
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 中的四个规则中的 none 说 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++ 规范中,我知道 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
抛出什么?
5.17 Throwing an exception
- A throw-expression with no operand rethrows the currently handled exception.
15.1 Throwing an exception
- 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
抓住了?
15.1 Throwing an exception
- 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
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.
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 中的四个规则中的 none 说 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)