异常处理(eh)似乎是当前的标准,通过搜索网页,我找不到任何新的想法或方法来改进或替换它(好吧,有些变化存在,但没有新颖之处)。
虽然大多数人似乎忽视它或只是接受它,但是eh有一些巨大的缺点:异常对代码是不可见的,它创建了许多可能的出口点。关于软件的乔尔写了一篇文章。与goto的比较是完美的,这让我重新思考了eh。
我尽量避免使用eh,只使用返回值、回调或任何符合目的的方法。但是,当您必须编写可靠的代码时,您现在不能忽略eh:它从new开始,它可能抛出一个异常,而不是返回0(就像以前那样)。这使得任何一行C++代码都容易受到异常的影响。然后C++基础代码中的更多地方抛出异常…std lib会这样做,依此类推。
这感觉就像在摇摇晃晃的地上行走。所以,现在我们不得不关注异常!
但很难,很难。您必须学会编写异常安全代码,即使您有一些使用它的经验,仍然需要重新检查任何一行代码以确保安全!或者您开始在任何地方放置try/catch块,这会使代码变得混乱,直到达到不可读的状态。
eh用一种在代码中创建许多可能的出口点的方法替换了旧的干净的确定性方法(返回值..),这种方法只有几个可理解的缺点,并且易于解决,如果您开始编写捕获异常的代码(在某个时刻您必须执行的操作),那么它甚至创建了许多路径。通过您的代码(catch块中的代码,考虑一个服务器程序,在该程序中您需要std::cerr….以外的日志记录工具)。嗯,有优势,但这不是重点。
我的实际问题:
- 你真的写了异常安全代码吗?
- 您确定最后一个"生产就绪"代码是异常安全的吗?
- 你能确定吗?
- 你知道和/或实际使用其他可行的方法吗?
- "eh替换了旧的干净的确定性方法(返回值..)"什么?异常和返回代码一样具有确定性。他们没有什么随机的。
- 长期以来,eh一直是标准(甚至从Ada开始!)
- "eh"是否是异常处理的既定缩写?我以前从没见过。
- @托马斯·帕德罗·麦卡锡:你还能怎么缩写"例外"呢?我的问题很长,我不想阻止人们阅读它,所以我把大多数时候使用的术语缩写了一下。嗯,我先用错了词,然后……嘿,我为什么要回答这个问题,你只是想迷惑我,不是吗?D
- 牛顿定律今天适用于大多数事情。事实上,他们已经预测了数百年的各种现象,这一点非常重要。
- @大卫·索恩利:是的,是的,返回错误代码和异常处理工作了好几年,接下来呢?
- @弗伦西:嘿,抱歉把你弄糊涂了!但我认为,如果有什么不同的话,一个奇怪的、无法解释的首字母缩写词或缩写词会使人们更加泄气。
- 有一个非常好的关于异常安全代码的视频在C++中
- C++- Jon Kalb中的异常安全编码;该对话甚至专门引用乔尔的文章,并展示了他的例子出错的地方。
你的问题表明,"编写异常安全代码非常困难"。我会先回答你的问题,然后再回答他们背后隐藏的问题。好的。回答问题
Do you really write exception safe code?
Ok.
当然,我知道。好的。
这就是为什么Java失去了它作为C++程序员的吸引力(缺少RAII语义),但我在解题:这是一个C++问题。好的。
实际上,当您需要使用STL或Boost代码时,这是必需的。例如,C++线程(EDCOX1 0)或EDCOX1(1))将抛出异常以优雅退出。好的。
Are you sure your last"production ready" code is exception safe?
Ok.
Can you even be sure, that it is?
Ok.
编写异常安全代码就像编写没有bug的代码。好的。
你不能百分之百地确定你的代码是异常安全的。但之后,您将努力实现它,使用众所周知的模式,并避免使用众所周知的反模式。好的。
Do you know and/or actually use alternatives that work?
Ok.
在C++中没有可行的替代方案(也就是说,你需要恢复到C,避免C++库,以及像Windows SEH这样的外部惊喜)。好的。编写异常安全代码
要编写异常安全代码,必须首先知道编写的每个指令的异常安全级别。好的。
例如,new可以抛出异常,但分配内置(例如in t或指针)不会失败。一个交换永远不会失败(不要写一个抛出的交换),一个std::list::push_back可以抛出…好的。例外担保
首先要了解的是,您必须能够评估所有职能部门提供的例外担保:好的。
没有:你的代码不应该提供这个。这段代码将泄漏所有信息,并在抛出第一个异常时崩溃。
基本原则:这是您必须提供的保证,即,如果抛出异常,则不会泄漏任何资源,并且所有对象仍然是完整的。
St强大:处理将成功,或引发异常,但如果抛出,则数据将处于与处理根本没有启动的相同状态(这给C++提供了事务性电源)。
否/否失败:处理将成功。
代码示例
下面的代码看起来像是正确的C++,但实际上,提供了"无"保证,因此,它是不正确的:好的。
1 2 3 4 5 6 7 8
| void doSomething(T & t)
{
if(std::numeric_limits<int>::max() > t.integer) // 1. nothrow/nofail
t.integer += 1 ; // 1'. nothrow/nofail
X * x = new X() ; // 2. basic : can throw with new and X constructor
t.list.push_back(x) ; // 3. strong : can throw
x->doSomethingThatCanThrow() ; // 4. basic : can throw
} |
我在编写代码时考虑了这种分析。好的。
提供的最低保证是基本的,但随后,每个指令的顺序使整个函数"无",因为如果是3。抛出,X将泄漏。好的。
首先要做的是使函数"基本",即将x放入智能指针中,直到它被列表安全拥有:好的。
1 2 3 4 5 6 7 8 9 10
| void doSomething(T & t)
{
if(std::numeric_limits<int>::max() > t.integer) // 1. nothrow/nofail
t.integer += 1 ; // 1'. nothrow/nofail
std::auto_ptr<X> x(new X()) ; // 2. basic : can throw with new and X constructor
X * px = x.get() ; // 2'. nothrow/nofail
t.list.push_back(px) ; // 3. strong : can throw
x.release() ; // 3'. nothrow/nofail
px->doSomethingThatCanThrow() ; // 4. basic : can throw
} |
现在,我们的代码提供了"基本"保证。不会有任何泄漏,所有对象都将处于正确状态。但我们可以提供更多,也就是说,强有力的保证。这就是它可能变得昂贵的原因,这就是为什么不是所有C++代码都很强大的原因。让我们试试看:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| void doSomething(T & t)
{
// we create"x"
std::auto_ptr<X> x(new X()) ; // 1. basic : can throw with new and X constructor
X * px = x.get() ; // 2. nothrow/nofail
px->doSomethingThatCanThrow() ; // 3. basic : can throw
// we copy the original container to avoid changing it
T t2(t) ; // 4. strong : can throw with T copy-constructor
// we put"x" in the copied container
t2.list.push_back(px) ; // 5. strong : can throw
x.release() ; // 6. nothrow/nofail
if(std::numeric_limits<int>::max() > t2.integer) // 7. nothrow/nofail
t2.integer += 1 ; // 7'. nothrow/nofail
// we swap both containers
t.swap(t2) ; // 8. nothrow/nofail
} |
我们重新订购了这些操作,首先创建和设置X的正确值。如果任何操作失败,那么t就不会被修改,因此,操作1到3可以被视为"强":如果有东西抛出,t就不会被修改,X也不会泄漏,因为它属于智能指针。好的。
然后,我们创建一个t的t2副本,并从操作4到7对该副本进行处理。如果有东西抛出,则修改t2,但随后,t仍然是原始的。我们仍然提供强有力的保证。好的。
然后,我们交换t和t2。交换操作应该在C++中不被抛出,所以让我们希望您为EDCOX1的1位写的交换是NoFoT(如果不是,重写它,这样它就不会被抛出)。好的。
所以,如果我们到达函数的末尾,一切都成功了(不需要返回类型),并且t有它的异常值。如果失败,那么t仍然有其原始价值。好的。
现在,提供强有力的保证可能是相当昂贵的,所以不要努力为所有代码提供强有力的保证,但是如果您可以不花费成本(并且C++内联和其他优化可以使所有代码都没有成本),那么就这样做。用户将为此感谢您。好的。结论
编写异常安全代码需要一些习惯。您需要评估您将要使用的每个指令提供的担保,然后,您需要评估一个指令列表提供的担保。好的。
当然,C++编译器不会支持这个保证(在我的代码中,我提供的保证是一个警告的doxGEN标签),这有点令人难过,但是它不应该阻止你尝试编写异常安全代码。好的。正常故障与故障
程序员如何保证一个不失败的函数总是成功的?毕竟,函数可能有一个bug。好的。
这是真的。异常保证应该由无缺陷代码提供。但是,在任何语言中,调用一个函数都假定该函数是无缺陷的。没有健全的代码可以保护自己不受出现错误的可能性的影响。尽你所能地编写代码,然后假设它是无缺陷的,提供保证。如果有错误,纠正它。好的。
异常是针对异常处理失败,而不是针对代码错误。好的。最后的话
现在,问题是"这值得吗?".好的。
当然是这样。拥有一个"无故障/无故障"功能,知道该功能不会失败是一个很大的好处。对于"强"函数也可以这样说,它使您能够使用事务语义编写代码,例如数据库,具有提交/回滚功能,提交是代码的正常执行,抛出异常是回滚。好的。
那么,"基本"是你应该提供的最起码的保证。C++是一种非常强大的语言,具有范围,使您能够避免任何资源泄漏(垃圾收集器会发现为数据库、连接或文件句柄提供困难)。好的。
所以,就我看来,这是值得的。好的。编辑2010-01-29:关于非抛出交换
Nobar做了一个我认为非常相关的评论,因为它是"如何编写异常安全代码"的一部分:好的。
- [我]交换永远不会失败(甚至不要写一个抛出的交换)
- [nobar]这是自定义编写的swap()函数的一个很好的建议。然而,应该注意的是,根据其内部使用的操作,std::swap()可能会失败。
默认的std::swap将生成副本和分配,对于某些对象,这些副本和分配可以抛出。因此,默认交换可以抛出,可以用于类,甚至用于stl类。就C++标准而言,如果EDCOX1、3、EDCOX1、4、EDCX1、5等的交换操作不会抛出,而如果EXOCX1的6个字可以与EXOCX1相关,则如果比较函数可以抛出拷贝构造(参见C++编程语言,特别版,附录E,E.4.3.SWAP)。好的。
在Visual C++ 2008实现向量交换时,如果两个向量具有相同的分配器(即正常情况),则向量的交换不会抛出,但如果它们具有不同的分配器,则将复制。因此,我假设它可以在最后一个案例中抛出。好的。
所以,原始文本仍然保持不变:永远不要编写抛出交换,但必须记住Nobar的评论:确保要交换的对象具有非抛出交换。好的。编辑2011-11-06:有趣的文章
Dave Abrahams为我们提供了基本的/strong/nothrow保证,他在一篇文章中描述了他在确保STL异常安全方面的经验:好的。
http://www.boost.org/community/exception_safety.html好的。
看看第7点(异常安全的自动测试),他依靠自动单元测试来确保每个案例都经过测试。我想这部分是对作者"你能确定吗?".好的。编辑2013-05-31:迪奥纳达尔的评论
t.integer += 1; is without the guarantee that overflow will not happen NOT exception safe, and in fact may technically invoke UB! (Signed overflow is UB: C++11 5/4"If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined.") Note that unsigned integer do not overflow, but do their computations in an equivalence class modulo 2^#bits.
Ok.
迪奥纳达尔指的是下面这一行,它的行为确实不明确。好的。
1
| t.integer += 1 ; // 1. nothrow/nofail |
这里的解决方案是在执行加法之前验证整数是否已经达到最大值(使用std::numeric_limits::max())。好的。
我的错误会出现在"正常失败与错误"部分,也就是说,一个错误。它不会使推理失效,也不意味着异常安全代码是无用的,因为不可能实现。你不能保护自己不受电脑关机,编译器错误,甚至你的错误,或其他错误的影响。你不能达到完美,但你可以尽量接近完美。好的。
我把迪奥纳达尔的评论牢记在心,修正了这段代码。好的。好啊。
- 型谢谢!我仍然希望有不同的东西。但我接受你的答案,坚持C++和你的详细解释。你甚至反驳了我的"这使得任何一个C++代码行都容易受到异常"的攻击。这种逐行分析毕竟是有意义的…我得考虑一下。
- 型只是写下一个想法,在我忘记它之前:然后用1)保证级别和2)一个术语"如果给定的异常发生在该行中,注释每一行代码是不是很好和舒适?可能就像第二层代码,只要程序离开"通常的代码路径",就会执行它。这将提供代码和错误处理的完全分离,在实践中,人们可以编写代码,并在第二步中编写错误处理(而不会将它们混合太多)。
- 型@frunsi:听起来像是一个很好的学习练习,但一般来说并不实际。一旦您学习了异常安全(上面是一个很好的答案),您就开始为自己看到安全和不安全的构造。
- 型……代码和错误处理的分离在某种程度上类似于eh所承诺的(除其他之外,我可以想象它的最初想法是从应用程序代码中消除错误处理的负担)。也许删除应该更明确一些,在第二个代码层中一行一行地执行,或者类似的操作。好吧,那就忘了文本编辑器,但这个想法很有趣…
- 型@大卫·索恩利:我知道异常安全(至少是基础知识和一些应用它的实践)。但我还是不喜欢。
- 型我是说…另一个注意事项:当异常处理意味着替换错误返回代码时,那么它意味着(简单地说)从应用程序代码中删除错误处理代码。所以,它(也很明显地说)是为了将错误处理代码和应用程序代码分开而发明的。所以,也许当前的EH概念是可行的,并且已经成熟了,但是可能有完全不同的解决方案(仍有待开发)。
- 型@弗伦西:谢谢你的评论。Try/Catch块已经将正常代码和错误处理代码分开,但我不认为应该更分开:错误处理代码有时必须访问正常代码(即局部变量)以检索有用的数据(可能再次尝试处理,或以不同方式尝试处理等)。当然,你是对的:当我写"它是C++唯一可行的"时,这是因为它是当前C++语言规范唯一可行的方法。研究很可能会导致其他的错误处理方法。
- 型@Frunsi:异常安全具有有趣的效果。例如,当你有一个只调用nofail/nothrow代码的函数时,当你意识到作为一个整体,这个函数也是nofail/nothrow,有一个"闪亮"的时刻,不知何故会让你的一天更加明亮。强函数也可以这样说。当您有一个看起来很有用的函数,并且您可以提供一个强大的/commit/rollback保证时,您可以从一个新的视角看到您的代码。我开始将SqtLe3C代码打包成C++代码,并提供对事务概念的强/抛出保证,既有趣又有价值。
- 型"互换永远不会失败"。这是自定义写入swap()函数的好建议。但是,应该注意,std::swap()可能会根据内部使用的操作而失败。
- 型@nobar:+1表示评论。我将编辑我的答案以添加您的贡献。
- 型Java没有EDOCX1,0Ω/EDCX1,1?
- 型Mehrdad确切地说,在Java中,同样的效果不是通过终结器实现的,而是通过EDCOX1×2个块实现的。真的,这个答案让我更加喜欢Java。泄漏内存要困难得多,异常已按其功能分为多个组,并且还存在已检查和未检查的异常(正确使用!)帮助很多人不要忘记处理异常。
- @马尔科姆:埃多克斯1〔0〕:你相信那是不对的。Java使得比C++更难有异常安全代码。例如,在C++中没有EDCOX1的1度,这不是一个错误…请参阅我的问题StaskOfFult.COM/Quass/194261,它是一个Java失败的例子,与C++和(当然)C++相比。
- @你能告诉我"失败"到底是什么吗?当然,语法是不同的,但看起来finally确实做到了您想要实现的目标。垃圾收集模型与C++完全不同——如果需要销毁,则使用终结器;如果需要在特定时间执行,则使用EDCOX1×1×块。仅仅因为它不符合你的口味(raii不符合我的口味,因为它需要一个新的结构,每一个darn时间,这有时是乏味的)并不意味着它是"失败"。
- @梅尔达:埃多克斯1〔4〕当然有。并且由于易于用EDCOX1〔1〕产生脆性代码,因此在Java 7中引入了"尝试与资源"的概念(即C 10的EDCOX1 6年之后和C++析构者后3年)。这就是我批评的脆弱性。至于EDCOX1,7个方面:在这一点上,这个行业不同意你的品味,因为垃圾收集的语言倾向于添加RAII启发的语句(C的EDCOX1 6)和Java的EDCOX1(9)。
- @芍药:你省略的江户十一〔十〕部分表明你完全没有理解我的评论。我从来没有说过我不喜欢using或新的try块。再次阅读我的评论,为什么我说我不喜欢RAII在C++中。
- @ Mehrdad:对于EDCOX1(13),请再次引用链接的问题,以及如何在不使用Java 7 EDCOX1×14模式的情况下,在Java中的相同函数中使用多个资源时,几乎不可能产生健壮的代码。
- @Mehrdad:RAII doesn't match mine, since it needs a new struct every single darn time, which is tedious sometimes:不,不是。你可以使用智能指针或实用程序类来"保护"资源。
- @paercebal:当使用多个资源时,几乎不可能生成健壮的代码。不,这很容易。每个资源只有1个"finally"块可以实现这一点。关于你的第二条评论:注意"有时"。显然,如果有人已经为您编写了代码,那么它就不适用了。但是我的评论的重点是它迫使你为一些你可能不需要包装的东西做一个包装,就像我说的,我觉得很乏味。我不是想告诉你我是对的,我只是告诉你这是两种语言的品味问题,而不是正确性问题。
- @paercebal:我还应该注意,拥有一个好的IDE会让Try/Finally变得非常简单。(例如,C的EDCOX1 16条片段基本上是在你按TAB时为你写的东西)。如果有的话,我一般喜欢C++胜过Java。但我不认为"尝试/最终"不足以编写正确的代码;这只是一件乏味的事情。(很容易在资源获取代码之外加上一组大括号,向读者发出信号,在try块启动之前他不应该做任何事情,如果需要的话。)
- @好吧,那就不用讨论你的口味了。
- @Mehrdad:"它强制您为一些您可能不需要包装器的东西制作包装器"-当以raii方式编码时,您将不需要任何类型的包装器:资源是对象,对象生存期是资源生存期。然而,我来自C++世界,我目前正与Java项目进行斗争。与C++中的RAII相比,Java中的"手动资源管理"是一团糟!我的看法是,JAVA早就把"自动内存MGMT"换成"自动资源管理"。
- 关于使用EDCOX1×0,并依赖于堆栈展开,如果您的堆栈没有覆盖EDCOX1×1的块,则C++标准将它定义为未定义的。据我所知,如果不启用编译器开关,GCC和VisualC++都不会在没有try块的异常之后执行堆栈展开。对于单线程应用程序来说,这不是一个问题(在主方法中最终会有一个EDCOX1×2块),但是在引入C++ 11 MT结构(EDCOX1,4,5,EDCOX1,5)之后,堆栈展开是一个需要注意的问题。
- "t.integer+=1;//1。nothrow/nofail"如果t为空,这不会引发异常吗?
- 在实践中发现,更容易的方法是不要在代码上抛出构造函数和赋值运算符的异常。内存分配只有在整个操作系统都处于震荡状态时才会开始失败,在大多数情况下似乎不需要担心。
- @esrogs:"t.integer += 1; // 1. nothrow/nofail" Wouldn't this throw an exception if t is null?:t作为引用传递,因此它是一个值,而不是指针。在C++中,去引用空指针不会抛出异常,而是有未定义的行为(通常它在第一次真正使用时会崩溃)。
- @JeroenDirks:通常,内存分配失败将由新的操作符抛出,因此不需要自己从构造函数中手动抛出它们。现在,在某些情况下(例如,您使用malloc进行分配),如果您想要正确地操作,就需要手动抛出异常。通常,有更大的问题,但是如果你正在编写一个库,那么你就不能用"如果它成功了,那么对不起,我的代码只在正常情况下工作"这样的假设来编写代码。所以,即使操作系统正在遭受重创,抛出异常仍然是正确的选择。
- t.integer += 1;没有保证溢出不会发生,也不是异常安全的,实际上可以从技术上调用ub!(符号溢出是UB:C++11 5/4)如果在表达式的求值过程中,结果在数学上没有定义或不在其类型的可表示值范围内,行为是未定义的。""注意无符号整数不溢出,而是在等价类2模^位中进行计算。
- +我迄今为止看到的最好的实际解释,但我不知道如何在代码中实现安全的eh。只要看这三个例子,就很容易理解第一个。在没有注释的情况下,我的头脑在思考什么是主代码逻辑,什么是最后两个示例中的eh:代码不再通过rubber-ducky测试。imho,这种意外的混淆很容易导致代码损坏。是否有任何方法可以编写易于读取/维护的eh代码?我担心,如果我做的不对,它将导致代码脆弱/一半失败,而不是一个大声失败的代码。
- @kfffe04:这比看起来容易,但是像所有的事情一样,熟悉它需要一些工作:我在一个完整的库(一个STL样的树结构)上工作,以便对这个概念感到轻松。我的建议是:保持函数小而简单(让它们做一件事),不做任何假设(除了基本保证,即RAII),并学会识别每一行的保证。使用简单的功能,您将能够识别出担保水平…现在,对于更大的功能,它会更复杂,但这是可以的,因为基本的保证几乎是自动的C++。
- @迪奥纳达尔:+1:感谢您的输入。我会更新我的答案。
- "没有健全的代码可以保护自己不受出现错误的可能性。"我想我的代码是疯狂的。)
- @伦纳特罗兰德+1我正要写几乎一样的东西。在很多领域中使用C++,即使系统中存在代码错误,系统也应该保证安全执行(尽可能多)。尤其是,许多安全机制实际上是通过添加额外的防御层和检查/验证来保护未知漏洞的利用。
- auto_ptr不推荐使用,应替换为unique_ptr。无论如何,我不认为我的C++技能足够强大来编辑这个伟大的社区维基邮报,因为我不是舒尔关于它可能隐藏的任何暗示。
- @罗布森:你说得对。这是C++ 98代码,但我发现它是一个更好的例子。在C++ 11中,你可以做一个EDCOX1×2,所以释放调用是无用的(这是一件好事,imHO)。但本文的重点是解释,在目前的情况下,"nothrow"函数在函数体中的移动。我想知道如果我不能并排放置C++ 98/C++ 11代码,或者在C++的最后一个例子中添加一个编辑…
- 编译代码时如何禁用异常?[谷歌C++风格指南](谷歌.GITHUB.IO/StultGueld/CppGuth.html)说明它们不使用异常。在这种情况下如何编程?
- @ AlimaCieldMe:谷歌C++风格指南是一个废话,所以我不建议使用它。现在,异常安全的代码样式也可以用于不抛出异常的代码,因为它还保护您不受早期返回、中断、继续等的影响。如果代码可以处理异常,那么它几乎可以处理任何同步的事情。
- 不是为了使用,而是为了好奇,如果有什么东西(例如新的)抛出,他们会怎么做?谷歌风格没有意义。据我所知,禁用异常只会中止。他们不能使用任何可能抛出的东西,包括带有"new"的内存分配,或者他们假设一个无限的内存,只是很奇怪——我不明白。
在C++中编写异常安全代码与使用大量的尝试{ } catch {}块无关。它是关于记录您的代码提供了什么样的保证。
我建议阅读Herb Sutter的"本周大师"系列,特别是59、60和61期。
总而言之,您可以提供三个级别的异常安全:
- 基本:当代码抛出异常时,代码不会泄漏资源,对象仍然是可销毁的。
- 强:当代码抛出异常时,应用程序的状态保持不变。
- 不抛出:您的代码从不抛出异常。
就我个人而言,我发现这些文章已经很晚了,所以我的C++代码绝对不例外。
- 他的书《例外C++》也是一本很好的读物。但我仍然试图质疑呃…
- +1 op将处理异常(捕获异常)与异常安全(通常更多关于raii)相结合。
- 我怀疑很少的生产C++代码是异常安全的。
我们中的一些人使用这个例外已经超过20年了。例如,pl/i有它们。他们是一种新的危险技术的前提在我看来是可疑的。
- 请不要误会我,我正在(或试图)询问呃。特别是C++ EH。我正在寻找替代品。也许我必须接受它(如果这是唯一的方法,我也会接受),但我认为有更好的选择。这并不是说我认为这个概念是新的,但是的,我认为它可能比用返回代码进行显式错误处理更危险…
- 如果你不喜欢,就不要用它。在需要调用的可以抛出的东西周围放置try块,重新创建旧的错误代码,并忍受它所存在的问题,在某些情况下,这些问题是可以容忍的。
- 好的,完美的,我只会使用eh和错误代码,并使用它。我是个笨蛋,我应该自己来解决这个问题!;)
首先(如尼尔所说),SEH是微软的结构化异常处理。它与C++中的异常处理类似但不完全相同。事实上,如果要在VisualStudio中使用C++,则必须启用C++异常处理——默认行为不能保证本地对象在所有情况下都被破坏!在这两种情况下,异常处理并不是很困难,只是不同而已。
现在请回答您的实际问题。
Do you really write exception safe code?
对。我在所有情况下都努力寻找异常安全的代码。我使用RAII技术来传播对资源的作用域访问(例如,用于内存的boost::shared_ptr,用于锁定的boost::lock_guard)。通常,RAII和范围保护技术的一致使用将使异常安全代码更容易编写。诀窍是了解存在的东西以及如何应用它。
Are you sure your last"production ready" code is exception safe?
不,它和它一样安全。我可以说,在几年的24/7活动中,我没有看到由于异常而导致的流程故障。我不期望完美的代码,只是编写良好的代码。除了提供异常安全性外,上述技术还保证了正确性,这种方法几乎不可能用try/catch块实现。如果您捕获了顶部控制范围(线程、进程等)中的所有内容,那么您可以确保在遇到异常时(大多数情况下)继续运行。同样的技术也将帮助您在遇到异常时继续正确地运行,而不需要在任何地方使用try/catch块。
Can you even be sure that it is?
对。你可以通过一个彻底的代码审计来确定,但是没有人真的这样做?不过,定期的代码审查和谨慎的开发人员要想达到这一目标还有很长的路要走。
Do you know and/or actually use alternatives that work?
这些年来,我尝试过一些变化,比如高位编码状态(ala HRESULTs)或可怕的setjmp() ... longjmp()黑客。这两种情况在实践中都有不同的分解方式。
最后,如果您养成了应用一些技术的习惯,并仔细考虑在哪里可以对异常做出响应,那么您将得到非常可读的代码,这是异常安全的。您可以按照以下规则进行总结:
- 你只想看到try/catch,当你可以对一个特定的异常做些什么的时候。
- 您几乎不想在代码中看到原始的new或delete。
- 避开std::sprintf、snprintf和数组,一般使用std::ostringstream来格式化数组,并用std::vector和std::string替换数组。
- 如果有疑问,请在滚动之前在boost或stl中查找功能
我只能建议您学习如何正确使用异常,如果您计划用C++编写,则忘记结果代码。如果你想避免例外,你可以考虑用另一种语言来写,要么没有例外,要么使例外安全。如果你想真正学会如何充分利用C++,请阅读萨特、Nicolai Josuttis和Scott Meyers的几本书。
- "默认行为并不保证本地对象在所有情况下都被破坏",您是说VisualStudioC++编译器的默认设置会产生在异常时不正确的代码。是真的吗?
- "你几乎不想在代码中看到一个原始的new或delete":通过raw,我猜你的意思是在构造函数或析构函数之外。
- @raedward-re:vc++:当抛出SEH异常时,vs2005版本的vc++不会破坏本地对象。阅读"启用C++异常处理"。在VS2005中,默认情况下,SEH异常不调用C++对象的析构函数。如果您调用win32函数或在C接口dll中定义的任何东西,那么您必须担心这一点,因为它们可以(有时也会)以您的方式抛出一个SEH异常。
- @Raedward:re:raw:基本上,delete不应该在tr1::shared_ptr等的实现之外使用。只要其用法类似于tr1::shared_ptr ptr(new X(arg, arg));,就可以使用new。重要的是,new的结果直接进入托管指针。boost::shared_ptr最佳实践页面描述了最佳实践。
- 为什么在您的异常安全规则集中引用std::sprintf(et al)?这些文件不会记录它们抛出的任何异常,例如en.cppreference.com/w/cpp/io/c/fprintf
在假设"任何行都可以抛出"的情况下,不可能编写异常安全代码。异常安全代码的设计关键依赖于某些合同/保证,这些合同/保证是您应该在代码中期望、观察、遵循和实现的。绝对有必要拥有保证永远不会抛出的代码。还有其他类型的例外保证。
换句话说,创建异常安全代码在很大程度上是程序设计的问题,而不仅仅是简单编码的问题。
嗯,我当然想。
我确信我使用异常构建的24/7服务器可以24/7运行,并且不会泄漏内存。
很难确定任何代码是否正确。通常,一个人只能通过结果
不需要。使用异常比我在过去30年中在编程中使用的任何替代方法都更简单、更干净。
- 这个答案没有价值。
- @那么问题就没有价值了。
- 汤匙不存在
撇开SEH和C++异常之间的混淆,您需要知道在任何时候都可以抛出异常,并将代码写入其中。对异常安全性的需求很大程度上推动了RAII、智能指针和其他现代C++技术的使用。
如果您遵循良好的模式,那么编写异常安全的代码并不特别困难,事实上,它比在所有情况下正确地编写处理错误返回的代码更容易。
一般来说,eh是好的。但是C++的实现并不十分友好,因为很难说你的异常捕获覆盖率有多高。例如Java使得这很容易,如果不处理可能的异常,编译器会失败。
你真的写了异常安全代码吗?[没有这样的事。除非您有一个受管理的环境,否则异常是对错误的一种保护。这适用于前三个问题。]
你知道和/或实际使用其他可行的方法吗?[替代什么?这里的问题是人们不能将实际错误与正常程序操作分开。如果它是正常的程序操作(即找不到文件),它就不是真正的错误处理。如果这是一个实际的错误,就没有办法"处理"它,或者它不是一个实际的错误。你的目标是找出哪里出了问题,或者停止电子表格并记录错误,重新启动你的烤面包机的驱动程序,或者祈祷喷气式战斗机可以继续飞行,即使它的软件是小车,并希望最好。]
是的,我尽力编写异常安全代码。
这意味着我要注意哪些线可以抛。不是每个人都能做到,记住这一点至关重要。关键是要考虑并设计代码以满足标准中定义的异常保证。
是否可以编写此操作来提供强异常保证?我必须适应基本的生活吗?哪些行可能会引发异常,我如何确保它们不会损坏对象?
我们中的一些人喜欢像Java这样的语言,迫使我们声明方法所抛出的所有异常,而不是像C++和C语言那样使它们隐形。
如果处理得当,异常会优于错误返回代码,如果不是其他原因,您不必手动将失败传播到调用链上。
也就是说,低级API库编程应该避免异常处理,并坚持错误返回代码。
我的经验是,在C++中很难编写干净的异常处理代码。我经常使用new(nothrow)。
- 你也在避免使用大多数标准库?使用new(std::nothrow)是不够的。顺便说一下,在C++中编写异常安全代码比在Java:En.WiKiTo.Org/WiKi/RealthCyPosithOffice Isx初始化更容易。
- Java检查异常可用性被高度夸大了。事实上,非Java语言,它们并不被认为是成功的。这就是为什么C++中的"抛出"语句现在被认为是过时的,并且C从不认真考虑实现它们(这是一个设计选择)。对于Java,下面的文档可能会被激发:GoGoGestEng.BogSPo.com/2009/09/& Helip;
- 我的经验是,在C++中编写异常安全代码并不难,而且它通常会导致更干净的代码。当然,你必须学会去做。
我非常喜欢使用Eclipse和Java(new to java),因为如果缺少EH处理程序,它会在编辑器中抛出错误。这使得事情很难忘记处理异常…
另外,使用IDE工具,它会自动添加try/catch块或另一个catch块。
- 这就是检查(Java)和未选中(C++)异常之间的区别(Java也有一些)。检查异常的优点在于您所写的内容,但在这里它有自己的缺点。谷歌为不同的方法和他们有不同的问题。
很多人(我甚至会说大多数人)都这么做。
异常真正重要的是,如果不编写任何处理代码,那么结果是完全安全且行为良好的。太急于惊慌,但安全。
您需要主动地在处理程序中犯错误以获得不安全的内容,只有catch(…)将与忽略错误代码进行比较。
- 不是真的。编写代码非常容易,这并不是异常安全的。例如:f = new foo(); f->doSomething(); delete f;如果dosomething方法抛出异常,那么就出现内存泄漏。
- 你的程序什么时候终止都没关系,对吧?要继续执行,您必须主动地接受异常。嗯,有些特定的情况下,没有清理的终止仍然是不可接受的,但是这种情况需要在任何编程语言和样式中特别注意。
- 您不能忽略异常(而不是写处理代码),既不在C++中,也不在托管代码中。它将是不安全的,而且不会表现得很好。除了一些玩具代码。
- 我说你可以吗?现在再读一遍我写的,试着去理解。
- 除了例外,不安全的代码是由于您在处理代码时出错造成的。对于错误代码,不安全的代码是由于您没有编写处理代码造成的。差别很大。
- 你写了"[…]如果你不写任何处理代码-结果是完全安全和行为良好的[…]"。因此,这听起来像"即使忽略异常,也可以编写完全安全且行为良好的代码"。不处理异常=忽略它们。当然,它与错误代码的情况类似。但这不是重点。您刚才写道,忽略异常结果是安全且行为良好的代码;-)但是,是的,我理解您的意图。
- 如果忽略应用程序代码中的异常,那么当涉及外部资源时,程序可能仍然表现不好。是的,操作系统关心关闭文件句柄、锁、套接字等。但并不是所有的事情都处理好了,例如,它可能会留下不必要的文件,或者在写入文件时损坏文件等等。如果忽略异常,爪哇中存在问题,C++中可以使用RAII(但是当使用RAII技术时,您最可能使用它们是因为您关心异常)…
- 请不要曲解我的话。我写了"如果你不写任何处理代码的话"——我的意思就是。要忽略异常,需要编写代码。
- 您的注释的问题是,异常安全中的错误很可能在处理代码之前发生。克里斯托弗的例子就是这样。