RAII与例外 我们在C++中使用RAII越多,我们发现析构函数就越做越小。现在,解除分配(finalization,不管你怎么称呼它)可能会失败,在这种情况下,异常是让楼上任何人知道我们的解除分配问题的唯一方法。但是,同样,抛出析构函数是一个坏主意,因为在堆栈展开期间可能会抛出异常std::uncaught_exception()让您知道什么时候会发生这种情况,但不会更多,因此除了让您在终止前记录消息之外,您也无能为力,除非您愿意让程序处于未定义的状态,其中一些内容被释放/完成,而另一些则没有
一种方法是不使用抛出析构函数。但在许多情况下,这只是隐藏了一个真正的错误。例如,我们的析构函数可能由于抛出了一些异常而关闭一些RAII管理的DB连接,而这些DB连接可能无法关闭。这并不一定意味着我们可以在此时终止程序。另一方面,记录和跟踪这些错误并不是所有情况下的解决方案;否则我们就没有必要从例外开始。 在没有抛出析构函数的情况下,我们还发现自己必须创建“reset()”函数,这些函数应该在销毁之前被调用——但这恰恰违背了RAII的全部目的 另一种方法是直接去做,因为这是你能做的最可预测的事情 有些人建议链接异常,以便一次可以处理多个错误。但我真的从来没有真正看到过C++中所做的,我不知道如何实现这样的事情。 所以不是RAII就是例外。不是吗?我倾向于不扔破坏者;主要是因为它使事情变得简单(r)。但我真的希望有一个更好的解决方案,因为正如我所说的,我们使用RAII越多,我们发现自己使用的DTR做的事情就越多 附录 我正在添加我发现的有趣的主题文章和讨论的链接:RAII与例外 我们在C++中使用RAII越多,我们发现析构函数就越做越小。现在,解除分配(finalization,不管你怎么称呼它)可能会失败,在这种情况下,异常是让楼上任何人知道我们的解除分配问题的唯一方法。但是,同样,抛出析构函数是一个坏主意,因为在堆栈展开期间可能会抛出异常std::uncaught_exception()让您知道什么时候会发生这种情况,但不会更多,因此除了让您在终止前记录消息之外,您也无能为力,除非您愿意让程序处于未定义的状态,其中一些内容被释放/完成,而另一些则没有,c++,exception,raii,destructor,C++,Exception,Raii,Destructor,一种方法是不使用抛出析构函数。但在许多情况下,这只是隐藏了一个真正的错误。例如,我们的析构函数可能由于抛出了一些异常而关闭一些RAII管理的DB连接,而这些DB连接可能无法关闭。这并不一定意味着我们可以在此时终止程序。另一方面,记录和跟踪这些错误并不是所有情况下的解决方案;否则我们就没有必要从例外开始。 在没有抛出析构函数的情况下,我们还发现自己必须创建“reset()”函数,这些函数应该在销毁之前被调用——但这恰恰违背了RAII的全部目的 另一种方法是直接去做,因为这是你能做的最可预测的事情
- StackOverflow算法探讨
- StackOverflow讨论(谢谢,Martin York)
- 这也涉及异常链接
- 为什么它没有你想象的那么有用
- 与有趣的参与者(长!)
- 斯特罗斯特鲁普解释
- 安德烈·亚历山德雷斯库
- 您不应该从析构函数中抛出异常
注:更新为参考标准中的变更:
在C++03中
如果异常已在传播,则应用程序将终止 在C++11中
如果析构函数是
noexcept
(默认值),则应用程序将终止
以下内容基于C++11
如果异常转义了noexcept
函数,则如果堆栈甚至被解卷,则它是实现定义的
以下内容基于C++03
我的意思是立即停止。堆栈放卷停止。不再调用析构函数。都是坏东西。请参见此处的讨论
我不同意您的逻辑,即这会导致析构函数变得更复杂。通过正确使用智能指针,这实际上使析构函数变得更简单,因为现在一切都是自动的。每个班级都有自己的小拼图。这里没有脑部手术或火箭科学。雷伊又赢了一场
至于std::uncaught_exception()的可能性,我指给您看,您可以通过检查来判断当前是否存在正在运行的异常(例如,我们在抛出和捕获块之间执行堆栈展开,可能是复制异常对象或类似操作)
bool std::uncaught_exception()
如果返回true,此时抛出将终止程序,否则抛出是安全的(或者至少与以前一样安全)。ISO 14882(C++标准)第15.2节和第15.5.3节对此进行了讨论
这并不能回答在清除异常时遇到错误时该怎么办的问题,但实际上并没有什么好的答案。但是,如果您在后一种情况下等待做一些不同的事情(如记录并忽略它),而不是简单地惊慌失措,它确实可以让您区分正常退出和异常退出。与原始问题不同: 现在,解除分配(最终确定, 不管你怎么称呼它)都可能失败, 在这种情况下,例外情况实际上是 让任何人上楼的唯一方法 知道我们的解除分配问题吗 清理资源失败表示:
例如,C++空闲存储有一个无失败操作符删除。其他API(如Win32)提供错误代码,但只会由于程序员错误或硬件故障而失败,错误指示堆损坏或双重释放等情况
对于不可恢复的不利条件,采用DB连接。如果由于连接断开而导致关闭连接失败--很好,您就完成了。不要扔!断开的连接(应该)会导致连接关闭,因此无需执行任何其他操作。如果有任何问题,请记录跟踪消息以帮助诊断使用问题。例如:class DBCon{
public:
DBCon() {
handle = fooOpenDBConnection();
}
~DBCon() {
int err = fooCloseDBConnection();
if(err){
if(err == E_fooConnectionDropped){
// do nothing. must have timed out
} else if(fooIsCriticalError(err)){
// critical errors aren't recoverable. log, save
// restart information, and die
std::clog << "critical DB error: " << err << "\n";
save_recovery_information();
std::terminate();
} else {
// log, in case we need to gather this info in the future,
// but continue normally.
std::clog << "non-critical DB error: " << err << "\n";
}
}
// done!
}
};
classdbcon{
公众:
DBCon(){
handle=foooopendbconnection();
}
~DBCon(){
int err=foodclosedbconnection();
如果(错误){
if(err==E_fooConnectionDropped){
//
vector<DBHandle> to_be_closed_later; // startup reserves space
DBCon::~DBCon(){
int err = fooCloseDBConnection();
if(err){
..
else if( fooIsRetryableError(err) ){
try{
to_be_closed.push_back(handle);
} catch (const bad_alloc&){
std::clog << "could not close connection, err " << err << "\n"
}
}
}
}
void doSomething()
{
try
{
MyResource A, B, C, D, E ;
// do something with A, B, C, D and E
// Now we quit the scope...
// destruction of E, then D, then C, then B and then A
}
catch(const MyResourceException & e)
{
// Do something with the exception...
}
}
catch(const std::vector<MyResourceException> & e)
{
// Do something with the vector of exceptions...
// Let's hope if was not caused by an out-of-memory problem
}