我们使用RAII在C ++的越多,我们就越发现自己与做不平凡的释放析构函数。 现在,释放(定稿,但你怎么称呼它)可能会失败,在这种情况下的例外是真正让我们的释放问题的人在楼上知道的唯一途径。 但话又说回来,投掷析构函数是一个坏主意,因为的堆栈展开过程中被抛出的异常的可能性。 std::uncaught_exception()
可以让你知道当发生这种情况,但没有更多,所以除了让你登录一个消息终止前那里,除非你愿意在不确定状态离开你的程序不是什么可以做,在那里有些东西被释放/敲定,有些不是。
一种方法是没有掷析构函数。 但是,在许多情况下,只是隐藏了真正的错误。 我们的析构函数可能,例如,可以关闭一些RAII管理数据库连接一些例外的结果被抛出,而那些DB连接可能无法关闭。 这并不意味着我们确定该程序在此点终止。 在另一方面,记录和跟踪这些错误是不是真的对每个案件的解决方案; 否则我们将不得不不需要例外开始。 由于没有掷析,我们也发现自己不必创建“重设()”,都应该功能破坏之前被称为 - 但只是违背了RAII的整个目的。
另一种方法是只是为了让程序终止 ,因为这是你能做的最可预测的事情。
有人建议链例外,让一个以上的误差可以在同一时间内处理。 但老实说,我从来没有真正看到用C是做++和我不知道如何实现这样的事情。
所以,它要么RAII或例外。 难道不是吗? 我倾向于没有掷析构函数; 这主要是因为它让事情变得简单(R)。 但我真的希望有一个更好的解决办法,因为,正如我所说,我们越使用RAII,我们发现自己使用做不平凡的事dtors就越多。
附录
我添加链接到有趣的话题的文章和讨论,我发现:
- 投掷析构函数
- 在StackOverflow的讨论与SEH问题
- 在计算器上讨论投掷析 (感谢马丁纽约)
- 乔尔在异常
- SEH是有害的
- CLR异常处理这也倒是异常链
- 香草萨特性病:: uncaught_exception ,为什么它并不像你想的那样有用
- 历史的讨论与有趣的参与者物(长!)
- 斯特劳斯解释RAII
- 安德烈Alexandrescu的的范围卫队
你不应该抛出一个异常出一个析构函数。
注意:更新后refeclt标准的变化:
在C ++ 03
如果一个异常已经传播则应用程序将终止。
在C ++ 11
如果析构函数是noexcept
(默认值),那么应用程序将终止。
以下是基于C ++ 11
如果一个异常逃脱一个noexcept
功能是如果堆栈甚至解开实现定义。
以下是基于C ++ 03
通过终止我的意思是立即停止。 堆栈展开停止。 没有更多的析构函数被调用。 所有不好的东西。 看到这里的讨论。
抛出异常出一个析构函数
我不明白(如不同意)你的逻辑,这导致析构函数变得更加复杂。
随着智能指针的正确使用这实际上使得析构函数简单,因为现在一切都变成自动的。 每个类潮汐了自己的小一块拼图。 无脑手术或在这里的火箭科学。 另一大胜利为RAII。
至于)性病的可能性:: uncaught_exception(我点你在香草Sutters文章为什么它不工作
从原来的问题:
现在,释放(定稿,但你怎么称呼它)可能会失败,在这种情况下的例外是真正让我们的释放问题的人在楼上知道的唯一途径
未清理的资源要么表示:
编程错误,在这种情况下,你应该登录失败,随后通知用户或终止应用,根据应用场景。 例如,释放已经被释放的分配。
分配器错误或设计缺陷。 查阅文档。 机会是错误可能是有帮助诊断程序员的错误。 见上文第1项。
否则不可恢复的不利条件,可以继续进行。
例如,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!
}
};
这些条件都不证明在尝试第二种退绕。 无论是程序可以继续正常(包括异常开卷,开卷是否正在进行中),或者它死在这里和现在。
编辑-添加
如果你真的希望能够保持某种链接到无法接近那些DB连接-也许他们未能收因间歇性的条件,并且希望稍后重新尝试-那么你可以随时清理推迟:
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"
}
}
}
}
非常不漂亮,但它可能把工作给你做。
这让我想起一个问题,从同事,当我解释他的异常/ RAII的概念:“嘿,我可以抛出什么异常,如果计算机的关闭”
无论如何,我同意马丁纽约的答案RAII与例外
什么是与异常和析构函数处理?
很多C ++的功能依赖于非投掷析构函数。
事实上,RAII的整个概念及其与代码分支合作(回报,抛出等)是基于这样的事实释放不会失败。 以同样的方式一些功能不应该失败(比如std ::交换)当您想提供高例外保证你的对象。
不,这并不意味着你无法通过析构函数抛出异常。 只是,语言甚至不会尝试为支持这种行为。
如果它被批准,会发生什么?
只是为了好玩,我试图想象它...
在你的析构函数不能释放你的资源的情况下,你会做什么? 你的对象可能遭到破坏的一半,你会从“外部”抓与信息呢? 再试一次? (如果是的话,为什么不能再从析构函数中尝试?......)
也就是说,如果你能无论如何访问一半遭到破坏的对象是:如果你的对象是在栈上(这是基本途径RAII工程)? 你怎么能访问它的范围之外的对象?
发送异常中的资源?
你唯一的希望是派中捕获异常,希望里面的代码资源的“处理”,还有......再次尝试释放它(见上文)?
现在,想象一些有趣:
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...
}
}
现在,让我们想象一下出于某种原因d的析构函数不能解除分配资源。 你编码它发送一个例外,那将被抓被抓。 一切顺利:您可以处理失败,你所希望的方式(你将如何以建设性的方式仍然逃避我,但后来,它不是现在的问题)。
但...
发送多个异常内的多个资源?
现在,如果〜d可以失败,那么〜C可也。 以及〜B和〜甲。
有了这个简单的例子,你有4分析其未能在“同一时刻”(退出范围)。 你需要的是不是不有一个例外抓,但有例外的数组的catch(让我们希望这不会......呃......扔生成的代码)。
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
}
让我们retarted( 我喜欢这首音乐......):每个抛出的异常是一个不同的( 因为原因不同的是:请记住,在C ++中,异常不必从的std ::异常派生 )。 现在,你需要同时处理四种例外。 你怎么能写catch子句按类型处理四种例外,并通过他们的顺序被抛出?
而如果你有相同类型的多个例外,通过多次失败的释放抛出? 而如果分配阵列之外数组的内存时,你的程序超出内存,呃...抛出内存溢出异常?
你确定你想花时间对这样的问题,而不是花钱盘算为何失败的释放或如何对其做出反应以另一种方式?
Apprently,C ++的设计者没有看到一个可行的解决方案,只是减少他们的损失有。
问题不在于RAII VS例外...
不,问题是,有时,事情会失败,以至于没有什么可以做。
RAII与例外的效果很好,只要某些条件得到满足。 其中:析构函数不会抛出 。 你们看到的作为反对派只是一个单一的模式结合两个“名”的极端情况:异常和 RAII
在析构函数中的一个问题发生的情况下,我们必须接受失败,并打捞什么可以挽救 :“数据库连接失败被释放对不起让我们至少避免此内存泄漏并关闭此文件?”。
虽然异常图案(应该是)在C ++中的主要的错误处理,这不是唯一的一个。 你应该处理异常(双关语意)情况下,当C ++异常不是一个解决方案,通过使用其他错误/日志机制。
因为你只是在语言,墙上没有,我知道的或听说过的其他语言遇到一堵墙通过正确地去,而不必关闭房子(C#的尝试是值得一个,而Java的一个仍然是伤害了我就在旁边笑话......我甚至不会谈论脚本语言谁将会在同一个沉默的方式相同的问题失败)。
但最终,无论你有多少代码写,你将不会被用户切换电脑电源保护 。
你能做的最好的,你已经写的。 我自己的喜好去与投掷finalize方法,非投掷析构函数清理资源无法手动完成,和日志/消息框(如果可能的话),以提醒在析构函数的失败。
也许你不搭右对决。 而不是“RAII与例外”,应该是“ 试图释放该绝对不想被释放,即使被破坏的威胁与资源资源 ”
:-)
您现在看到的两件事情:
- RAII,这就保证了资源的及时清理范围时退出。
- 完成操作,并找出它是否成功与否。
RAII承诺,它将完成操作(可用内存,关闭已经尝试的文件来进行冲洗,最后他试图提交它的事务)。 但是,因为它会自动发生,而不需要做任何程序员,它不会告诉程序员它的那些“企图”操作成功与否。
例外的是向大家报告,一些失败的一种方式,但正如你说的,还有C ++语言的限制,这意味着它们不适合这样做,从析构函数[*]。 返回值是另一种方式,但它更明显,析构函数也不能使用这些。
所以,如果你想知道你的数据是否被写入到磁盘,您不能使用RAII了点。 它没有“打败RAII的整个目的”,因为RAII仍然会尝试写它,它仍然会释放与文件句柄相关联的资源(DB交易,等等)。 它确实限制什么RAII可以做-它不会告诉你的数据是否被写入或没有,所以你需要一个close()
可以返回一个值和/或抛出一个异常功能。
[*]这是一个很自然的限制也存在于其它语言。 如果您认为RAII析构函数应该抛出异常说“出了问题!”,那么有些事情时,已经是在飞行中的例外,那就是发生“别的东西,甚至在此之前,已经错了!”。 我知道,使用异常不允许在飞行两个例外一次语言 - 语言和语法根本不允许它。 如果RAII是做你想要什么,然后自己需要异常被重新定义,以便它是有道理的,一个线程拥有多个件事的时间去错了,有两个例外向外传播和被称为两个处理,一个来处理每个。
其他语言允许第二个例外掩盖了第一,例如如果finally
块在Java中抛出。 C ++几乎说,第二个必须被抑制,否则terminate
被称为(同时抑制,在一定意义上)。 在两种情况下被告知这两个断层的更高水平的堆栈。 什么是有点可惜的是,在C ++中,你不能可靠地告诉一个多个异常是否是太多了( uncaught_exception
不告诉你,它会告诉你不同的东西),所以你甚至不能的情况下抛出有没有在飞行异常。 但是,即使你能在这种情况下做到这一点,你仍然可以在一个更是一个太多的情况下塞满。
有一件事我会问的是,无视终止等问题,你有什么感想适当的回应是,如果你的程序不能关闭其DB连接,无论是由于正常的破坏或特殊的破坏。
你似乎排除“仅仅记录”,并不愿意终止,所以你认为什么是最好的事情?
我想,如果我们有一个问题的答案那么我们将有一个如何进行更好的主意。
没有战略就显得尤为明显,我; 二话不说,我真的不知道这是什么意思关闭数据库连接抛出。 什么是,如果关闭()抛出的连接状态? 它是封闭的,仍处于打开状态,还是不确定的? 如果它是不确定的,是没有任何方法,程序返回到已知状态?
析构函数失败意味着没有办法撤消对象的创建; 该程序返回到已知的(安全)状态的唯一途径是推倒整个过程并重新开始。
什么是为什么你的破坏可能会失败的原因? 为什么不看看那些处理实际破坏之前?
例如,关闭一个数据库连接可能是因为:
- 交易正在进行中。 (检查的std :: uncaught_exception() - 如果为true,回滚,否则提交 - 这是最有可能所需的操作,除非你有说,否则政策,实际关闭连接之前)
- 连接断开。 (检测和忽略,服务器会自动回滚。)
- 其他DB错误。 (日志,所以我们可以研究并可能在未来适当的处理。这可能是检测和忽略。在此期间,尝试回滚并再次断开连接并忽略所有错误。)
如果我理解正确RAII(我可能没有),整点是它的范围。 所以它不是像你想交易持续超过对象不再反正。 这似乎是合理的我,那么,要确保关闭尽你所能。 RAII不会使这种独特的 - 即使没有在所有的(说,在C),你仍然试图抓住所有的错误条件,并作为最好的,你可以与他们打交道(有时忽略它们)对象。 所有RAII确实是迫使你把在一个地方所有的代码,无论多少功能是如何使用该资源类型。
你可以说当前是否存在飞行中的异常(如我们的罚球和catch块之间执行堆栈展开,也许复制的例外对象,或类似)通过检查
bool std::uncaught_exception()
如果返回true,在这一点上投掷将终止程序,如果不是,它是安全的,扔(或至少是安全的,因为它曾经是)。 这将在第15.2和ISO 14882(C ++标准)的15.5.3讨论。
这不回答,当你打一个错误,而清理异常怎么办的问题,但真的没有什么好回答了这一点。 但它确实让你正常退出和异常退出区分,如果你等待做不同的事情(如日志和忽略)在后一种情况下,而不是简单地panicing。
文章来源: RAII vs. exceptions