Does ScopeGuard use really lead to better code?
Asked Answered
P

8

33

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?

Paronym answered 7/9, 2008 at 18:20 Comment(2)
C++0x/C++11 does that now with "shared_ptr".Purport
I do see it giving you more power. The example with the database is pretty good. Using shared_ptr only it will call the destructor which usually only closes the connection while using ScopedGuard you can actually Rollback in case of an exception...Purport
C
63

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 ScopeGuards) isn't an obscure technique in C++ but firmly established best-practice.

Celka answered 7/9, 2008 at 18:30 Comment(1)
+1 for sticking up for modern c++ techniques. It should also be noted that Alexandrescu presented a new version of ScopeGuard here: channel9.msdn.com/Shows/Going+Deep/… which is much easier to use. Micht be worth an edit.Gant
L
30

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?

Lierne answered 15/2, 2009 at 14:22 Comment(0)
O
3

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.

Oldest answered 11/1, 2016 at 8:25 Comment(0)
F
2

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
Florinda answered 7/9, 2008 at 21:4 Comment(0)
C
2

Yes.

It was so important in C++ that even a special syntax for it in D:

void somefunction() {
    writeln("function enter");
    // c++ has similar constructs but not in syntax level
    scope(exit) writeln("function exit");

    // do what ever you do, you never miss the function exit output
}
Cosma answered 21/10, 2016 at 4:53 Comment(0)
H
1

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.

Hoggard answered 7/9, 2008 at 18:30 Comment(0)
H
-1

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.

Hakim answered 4/11, 2015 at 18:52 Comment(0)
B
-3

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

  • set GLcolor or GLwidth back to the original, once I've drawn a shape
  • make sure a file has fclosed once I had fopened it.
  • reset a mouse pointer to its initial state, after I've changed it to gears/hourgrlass during a execution of a slow function
  • reset the 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...

using simple RAII class

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.

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:

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.

using 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

    • 3 lines to keep previous value (state) and set it to a new one, and
    • 2 stack variables (previous_width and guard, again) to hold the previous state
  • the hand-crafted RAII class requires

    • 1 readable line to set new state and keep the previous one, and
    • 1 stack variable (guard) to hold the previous state.

Conclusion

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

Bathypelagic answered 13/11, 2020 at 15:57 Comment(1)
The scoped_width usage is awful and dangerous. You use two different commands to set the width (scoped_width(2) and setGLwidth(5)) and rely on comment to explain what the code does.Sydel

© 2022 - 2024 — McMap. All rights reserved.