从 C++ 异常的构造函数中抛出异常
Throwing an exception from the constructor of an exception in C++
请注意,这个问题与从异常 class 的构造函数 中抛出异常有关,而不是从任何旧的构造函数中抛出异常。我无法在 Whosebug 上找到重复的问题。
This article建议不要抛出这样的异常,但我对给出的技术原因持怀疑态度(而且作者似乎在评论中回溯了原因)。
我将举一个例子,然后讨论我对正在发生的事情的解释,这意味着这样做基本上不应该有问题,至少从技术角度来看是这样。我的主要问题是我的解释是否正确,或者我是否在概念上遗漏了什么。
例子:假设我想把所有可能的异常分为两种类型,User_Error
和Coder_Error
。前者显然表明用户做了他们不应该做的事情,而后者表明一些严重的内部检查失败并且有人应该提交错误报告。这是一个(显然过于简单的)草图:
#include <stdexcept>
#include <string>
...
class Coder_Error : public std::runtime_error
{
public:
Coder_Error( const std::string& msg ) :
std::runtime_error( msg + " Please freak out and file a bug report!" )
{}
};
class User_Error : public std::runtime_error
{
protected:
static void check_state() const
{
... // May rely on static members of this class or other classes.
if ( validation_failed ) {
throw Coder_Error( "A useful description of the problem." );
}
}
public:
User_Error( const std::string& msg ) : std::runtime_error( msg )
{
check_state();
}
};
现在假设我在 User_Error::check_state()
会抛出的情况下执行 throw User_Error( "Some useful message." );
。会发生什么?
解释: 我的期望是 Coder_Error
对象将在遇到 throw User_Error( "Some useful message." )
行中的 throw
之前被抛出,因此我们不必担心同时抛出两个异常,或类似的事情。更明确地说,我希望相关步骤按以下顺序发生。
User_Error::User_Error
会被调用。
User_Error::check_state
会被调用。
Coder_Error::Coder_Error
会被调用。
第 3 步中创建的 Coder_Error
对象将被抛出。
堆栈展开将开始,普通执行将停止,因此执行永远不会到达可以throw
第 1 步中创建的 User_Error
的点。(我假设没有错误被捕获。)
这是正确的吗?
据我所知,你是正确的,评估 throw User_Error(...)
有一个定义明确的结果并且不调用 std::terminate
,前提是 Coder_Error
对象被捕获通过封闭的处理程序。 GCC and Clang both agree.
你的推理对于 C++14 及更低版本是正确的。在 C++17 中,由于强制复制省略,User_Error
的构造函数在抛出异常的过程中被调用, 即, 一旦编译器已经开始评估throw
部分,并在某处为异常对象分配了一些存储空间。但是,当构造 通过 第二个异常退出时,编译器会清理该存储并继续为第二个异常寻找处理程序。无论哪种情况,结果都如你所说。
请注意,如果在异常的处理期间调用的复制构造函数通过异常退出,则将调用std::terminate
( 即, 因为异常对象被复制到按值捕获的处理程序中)(see example)。这与您描述的情况不同。
请注意,这个问题与从异常 class 的构造函数 中抛出异常有关,而不是从任何旧的构造函数中抛出异常。我无法在 Whosebug 上找到重复的问题。
This article建议不要抛出这样的异常,但我对给出的技术原因持怀疑态度(而且作者似乎在评论中回溯了原因)。
我将举一个例子,然后讨论我对正在发生的事情的解释,这意味着这样做基本上不应该有问题,至少从技术角度来看是这样。我的主要问题是我的解释是否正确,或者我是否在概念上遗漏了什么。
例子:假设我想把所有可能的异常分为两种类型,User_Error
和Coder_Error
。前者显然表明用户做了他们不应该做的事情,而后者表明一些严重的内部检查失败并且有人应该提交错误报告。这是一个(显然过于简单的)草图:
#include <stdexcept>
#include <string>
...
class Coder_Error : public std::runtime_error
{
public:
Coder_Error( const std::string& msg ) :
std::runtime_error( msg + " Please freak out and file a bug report!" )
{}
};
class User_Error : public std::runtime_error
{
protected:
static void check_state() const
{
... // May rely on static members of this class or other classes.
if ( validation_failed ) {
throw Coder_Error( "A useful description of the problem." );
}
}
public:
User_Error( const std::string& msg ) : std::runtime_error( msg )
{
check_state();
}
};
现在假设我在 User_Error::check_state()
会抛出的情况下执行 throw User_Error( "Some useful message." );
。会发生什么?
解释: 我的期望是 Coder_Error
对象将在遇到 throw User_Error( "Some useful message." )
行中的 throw
之前被抛出,因此我们不必担心同时抛出两个异常,或类似的事情。更明确地说,我希望相关步骤按以下顺序发生。
User_Error::User_Error
会被调用。User_Error::check_state
会被调用。Coder_Error::Coder_Error
会被调用。第 3 步中创建的
Coder_Error
对象将被抛出。堆栈展开将开始,普通执行将停止,因此执行永远不会到达可以
throw
第 1 步中创建的User_Error
的点。(我假设没有错误被捕获。)
这是正确的吗?
据我所知,你是正确的,评估 throw User_Error(...)
有一个定义明确的结果并且不调用 std::terminate
,前提是 Coder_Error
对象被捕获通过封闭的处理程序。 GCC and Clang both agree.
你的推理对于 C++14 及更低版本是正确的。在 C++17 中,由于强制复制省略,User_Error
的构造函数在抛出异常的过程中被调用, 即, 一旦编译器已经开始评估throw
部分,并在某处为异常对象分配了一些存储空间。但是,当构造 通过 第二个异常退出时,编译器会清理该存储并继续为第二个异常寻找处理程序。无论哪种情况,结果都如你所说。
请注意,如果在异常的处理期间调用的复制构造函数通过异常退出,则将调用std::terminate
( 即, 因为异常对象被复制到按值捕获的处理程序中)(see example)。这与您描述的情况不同。