使用 ScopeGuard 真的会带来更好的代码吗?
我多年前偶然发现了 Andrei Alexandrescu 和 Petru Marginean 撰写的这篇文章,其中介绍并讨论了称为 ScopeGuard 的实用程序类,用于编写异常安全代码。 我想知道使用这些对象进行编码是否真的会产生更好的代码,或者是否会混淆错误处理,因为也许守卫的回调会更好地呈现在 catch 块中? 有人有在实际生产代码中使用这些的经验吗?
I came across this article written by Andrei Alexandrescu and Petru Marginean many years ago, which presents and discusses a utility class called ScopeGuard for writing exception-safe code. I'd like to know if coding with these objects truly leads to better code or if it obfuscates error handling, in that perhaps the guard's callback would be better presented in a catch block? Does anyone have any experience using these in actual production code?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
是的。
它在 C++ 中非常重要,甚至在 D 中也有特殊的语法:
Yes.
It was so important in C++ that even a special syntax for it in D:
我没有使用过这个特定的模板,但我以前使用过类似的模板。 是的,与以不同方式实现的同样健壮的代码相比,它确实会导致更清晰的代码。
I haven't used this particular template but I've used something similar before. Yes, it does lead to clearer code when compared to equally robust code implemented in different ways.
我不得不说,不,不,事实并非如此。 这里的答案有助于证明为什么这是一个真正糟糕的想法。 资源处理应该通过可重用的类来完成。 他们通过使用作用域保护所实现的唯一一件事就是违反了 DRY 规则,并在整个代码库中复制了资源释放代码,而不是编写一个类来处理资源,然后就这样了。
如果范围保护有任何实际用途,那么资源处理不是其中之一。 在这种情况下,它们远远不如普通的 RAII,因为 RAII 是重复数据删除的,并且自动和范围防护是手动代码复制或破坏。
I have to say, no, no it does not. The answers here help to demonstrate why it's a genuinely awful idea. Resource handling should be done through re-usable classes. The only thing they've achieved by using a scope guard is to violate DRY up the wazoo and duplicate their resource freeing code all over their codebase, instead of writing one class to handle the resource and then that's it, for the whole lot.
If scope guards have any actual uses, resource handling is not one of them. They're massively inferior to plain RAII in that case, since RAII is deduplicated and automatic and scope guards are manual code duplication or bust.
我的经验表明,
scoped_guard
的使用远远不如您可以手工编写的任何简短的可重用 RAII 类。在尝试
scoped_guard
之前,我已经编写了 RAII 类来fclose
d 一旦我已经fopen
编辑了它。sorting
状态重置回其之前的状态暂时完成了更改其QListViewItems
- 我不希望每次更改单个项目的文本时列表都会重新排序...使用简单的 RAII 类
这是我的代码的样子 -精心设计的 RAII 类:
scoped_width
的实现非常简单,并且非常可重用。对于消费者来说也非常简单且可读。
使用
scoped_guard
(C++14)现在,使用
scoped_guard
,我必须捕获引入器 ([]
) 中的现有值为了将它传递给守卫的回调:上面的内容甚至在 C++11 上都不起作用。
更不用说试图以这种方式将状态引入 lambda 会伤害我的眼睛。
使用
scoped_guard
(C++11)在 C++11 中,您必须执行以下操作:
如您所见,
scoped_guard
snoppet 需要previous_width
和guard
)来保存先前的状态手工制作的
RAII类
需要guard
) 用于保存先前的状态。结论
我认为诸如此类的示例
并不能证明
scoped_guard
的有用性。我希望有人能告诉我为什么我没有从
scoped_guard
获得预期的收益。我相信通过编写简短的手工制作的类可以更好地利用 RAII,而不是使用更通用但难以使用的
scoped_guard
My experience shows that usage of
scoped_guard
is far inferior to any of the short reusable RAII classes that you can write by hand.Before trying the
scoped_guard
, I had written RAII classes tofclose
d once I hadfopen
ed it.sorting
state of a QListView's back to its previous state, once I've temporarily finished with altering itsQListViewItems
-- I did not want the list to reorder itself everytime I changed the text of a single item...using simple RAII class
Here's how my code looked like with my hand-crafted RAII classes:
Very simple implementation for
scoped_width
, and quite reusable.Very simple and readable from the consumer side, also.
using
scoped_guard
(C++14)Now, with the
scoped_guard
, I have to capture the existing value in the introducer ([]
) in order to pass it to the guard's callback:The above doesn't even work on C++11.
Not to mention that trying to introduce the state to the lambda this way hurts my eyes.
using
scoped_guard
(C++11)In C++11 you have to do this:
As you can see,
the
scoped_guard
snoppet requiresprevious_width
andguard
, again) to hold the previous statethe hand-crafted
RAII class
requiresguard
) to hold the previous state.Conclusion
I think that examples such as
are no proof of the usefullness of
scoped_guard
.I hope that somebody can show me why I don't get the expected gain from
scoped_guard
.I am convinced that RAII can be exploited better by writing short hand-crafted classes, than using the more generic but hard to use
scoped_guard
我经常使用它来保护内存使用,即从操作系统返回的需要释放的东西。 例如:
I often use it for guarding memory usage, things that need to be freed that were returned from the OS. For example:
我认为上述答案缺少一个重要的说明。 正如其他人指出的那样,您可以使用 ScopeGuard 来释放分配的资源,而不受故障(异常)的影响。 但这可能不是您想要使用作用域防护的唯一目的。 事实上,链接文章中的示例使用 ScopeGuard 来实现不同的目的:交易。 简而言之,如果您有多个需要保持某种相关状态的对象(即使这些对象正确使用 RAII),它可能会很有用。 如果任何这些对象的状态更改导致异常(我认为这通常意味着其状态没有更改),则需要回滚所有已应用的更改。 这会产生它自己的一系列问题(如果回滚也失败怎么办?)。 您可以尝试推出自己的类来管理此类相关对象,但随着这些对象数量的增加,它会变得混乱,并且您可能会回退到在内部使用 ScopeGuard 。
I think above answers lack one important note. As others have pointed out, you can use
ScopeGuard
in order to free allocated resources independent of failure (exception). But that might not be the only thing you might want to use scope guard for. In fact, the examples in linked article useScopeGuard
for a different purpose: transcations. In short, it might be useful if you have multiple objects (even if those objects properly use RAII) that you need to keep in a state that's somehow correlated. If change of state of any of those objects results in an exception (which, I presume, usually means that its state didn't change) then all changes already applied need to be rolled back. This creates it's own set of problems (what if a rollback fails as well?). You could try to roll out your own class that manages such correlated objects, but as the number of those increases it would get messy and you would probably fall back to usingScopeGuard
internally anyway.它肯定会改进你的代码。 您暂时提出的主张(它很晦涩并且代码值得使用
catch
块)在 C++ 中根本不正确,因为 RAII 是一种既定的习惯用法。 C++ 中的资源处理是通过资源获取来完成的,垃圾收集是通过隐式析构函数调用来完成的。另一方面,显式的 catch 块会使代码膨胀并引入微妙的错误,因为代码流变得更加复杂并且资源处理必须显式完成。
RAII(包括 ScopeGuard)在 C++ 中并不是一种晦涩的技术,而是牢固确立的最佳实践。
It definitely improves your code. Your tentatively formulated claim, that it's obscure and that code would merit from a
catch
block is simply not true in C++ because RAII is an established idiom. Resource handling in C++ is done by resource acquisition and garbage collection is done by implicit destructor calls.On the other hand, explicit
catch
blocks would bloat the code and introduce subtle errors because the code flow gets much more complex and resource handling has to be done explicitly.RAII (including
ScopeGuard
s) isn't an obscure technique in C++ but firmly established best-practice.是的。
如果有一段 C++ 代码我可以推荐每个 C++ 程序员花 10 分钟学习,那就是 ScopeGuard(现在是免费提供的 Loki 库)。
我决定尝试对我正在开发的小型 Win32 GUI 程序使用 ScopeGuard(稍作修改)版本。 您可能知道,Win32 有许多不同类型的资源,需要以不同的方式关闭(例如,内核句柄通常使用
CloseHandle()
关闭,GDI 需要BeginPaint()
关闭)与 EndPaint() 等配对)我将 ScopeGuard 与所有这些资源一起使用,并且还使用 new 分配工作缓冲区(例如,用于与字符集之间的字符集转换)统一码)。让我惊讶的是程序的短程度。基本上,这是双赢的:您的代码变得更短,同时更健壮。 未来的代码更改不会泄漏任何内容。 他们就是不能。 多么酷啊?
Yes.
If there is one single piece of C++ code that I could recommend every C++ programmer spend 10 minutes learning, it is ScopeGuard (now part of the freely available Loki library).
I decided to try using a (slightly modified) version of ScopeGuard for a smallish Win32 GUI program I was working on. Win32 as you may know has many different types of resources that need to be closed in different ways (e.g. kernel handles are usually closed with
CloseHandle()
, GDIBeginPaint()
needs to be paired withEndPaint()
, etc.) I used ScopeGuard with all these resources, and also for allocating working buffers withnew
(e.g. for character set conversions to/from Unicode).What amazed me was how much shorter the program was. Basically, it's a win-win: your code gets shorter and more robust at the same time. Future code changes can't leak anything. They just can't. How cool is that?