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 exce
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 use ScopeGuard
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 using ScopeGuard
internally anyway.
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.
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.
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 to
fclose
d once I had fopen
ed it.sorting
state of a QListView's back to its previous state, once I've temporarily finished with altering its QListViewItems
-- I did not want the list to reorder itself everytime I changed the text of a single item...Here's how my code looked like with my hand-crafted RAII classes:
class scoped_width {
int m_old_width;
public:
scoped_width(int w) {
m_old_width = getGLwidth();
setGLwidth(w);
}
~scoped_width() {
setGLwidth(m_old_width);
}
};
void DrawTriangle(Tria *t)
{
// GLwidth=1 here
auto guard = scoped_width(2); // sets GLwidth=2
draw_line(t->a, t->b);
draw_line(t->b, t->c);
draw_line(t->c, t->a);
setGLwidth(5);
draw_point(t->a);
draw_point(t->b);
draw_point(t->c);
} // scoped_width sets GLwidth back to 1 here
Very simple implementation for scoped_width
, and quite reusable.
Very simple and readable from the consumer side, also.
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:
void DrawTriangle(Tria *t)
{
// GLwidth=1 here
auto guard = sg::make_scoped_guard([w=getGLwidth()](){ setGLwidth(w); }); // capture current GLwidth in order to set it back
setGLwidth(2); // sets GLwidth=2
draw_line(t->a, t->b);
draw_line(t->b, t->c);
draw_line(t->c, t->a);
setGLwidth(5);
draw_point(t->a);
draw_point(t->b);
draw_point(t->c);
} // scoped_guard sets GLwidth back to 1 here
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.
scoped_guard
(C++11)In C++11 you have to do this:
void DrawTriangle(Tria *t)
{
// GLwidth=1 here
int previous_width = getGLwidth(); // explicitly capture current width
auto guard = sg::make_scoped_guard([=](){ setGLwidth(previous_width); }); // pass it to lambda in order to set it back
setGLwidth(2); // sets GLwidth=2
draw_line(t->a, t->b);
draw_line(t->b, t->c);
draw_line(t->c, t->a);
setGLwidth(5);
draw_point(t->a);
draw_point(t->b);
draw_point(t->c);
} // scoped_guard sets GLwidth back to 1 here
As you can see,
the scoped_guard
snoppet requires
previous_width
and guard
, again) to hold the previous statethe hand-crafted RAII class
requires
guard
) to hold the previous state.I think that examples such as
void some_function() {
sg::scoped_guard([](){ cout << "this is printed last"; }
cout << "this is printed first";
}
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
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()
, GDI BeginPaint()
needs to be paired with EndPaint()
, etc.) I used ScopeGuard with all these resources, and also for allocating working buffers with new
(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?
I often use it for guarding memory usage, things that need to be freed that were returned from the OS. For example:
DATA_BLOB blobIn, blobOut;
blobIn.pbData=const_cast<BYTE*>(data);
blobIn.cbData=length;
CryptUnprotectData(&blobIn, NULL, NULL, NULL, NULL, CRYPTPROTECT_UI_FORBIDDEN, &blobOut);
Guard guardBlob=guardFn(::LocalFree, blobOut.pbData);
// do stuff with blobOut.pbData