5 Replies - 1326 Views - Last Post: 30 December 2015 - 03:26 PM

#1 BetaWar  Icon User is offline

  • #include "soul.h"
  • member icon

Reputation: 1490
  • View blog
  • Posts: 8,217
  • Joined: 07-September 06

More advanced uses for scope guards

Posted 09 December 2015 - 03:08 PM

I am relatively sure that those of us who use C++ on a regular basis know about RAII, or the idea of acquiring/ releasing resources based on the construction/ destruction of an object because in C++ you are guaranteed that destructors will be called for constructed objects on the stack when they go out of scope.

This lead to the idea of ON_SCOPE_EXIT, which allows you to do more advanced things when going out of scope, such as calling a lambda (or some other callback function) pretty much making a generic scope exit function. However, that got me thinking about doing transactional things where it has to be all or nothing. We either succeed in the function and have a changed state, or we fail and have the same state. And that lead me to wanting a way to have a lambda automatically called on either a failure or success state too (instead of just scope exit).

While I was looking around the web for things like this I tripped over std::uncaught_exception (or in C++17 std::uncaught_exceptions) which allows you to determine if there is one or more uncaught exceptions in flight at the time without having to catch and rethrow your exceptions all the way up the call stack. That makes it relatively trivial to implement SCOPE_FAILURE and SCOPE_SUCCESS on top of SCOPE_EXIT.

Here's the code (note -- it requires C++11 support, and doesn't automatically determine the C++ version to call the C++17 variant if available):
#include <exception>
#include <functional>

enum ScopeExitRun
{
    RUN_ON_SUCCESS = 1,
    RUN_ON_FAILURE = 2,
    RUN_ALWAYS     = 3
};

class ScopeExit
{
public:
    ScopeExit(ScopeExitRun whenToRun): mWhenToRun(whenToRun), mCallback([](){}) {}
    ~ScopeExit(void)
    {
        int exceptions = std::uncaught_exception();
        if (mWhenToRun == RUN_ALWAYS ||
                (mWhenToRun == RUN_ON_FAILURE && exceptions > 0) ||
                (mWhenToRun == RUN_ON_SUCCESS && exceptions == 0)
           )
        {
            mCallback();
        }
    }
    void operator=(std::function<void(void)> callback)
    {
        mCallback = callback;
    }
private:
    ScopeExitRun mWhenToRun;
    std::function<void(void)> mCallback;
};

#define CONCAT(a, b ) a##b
#define SCOPE_HELPER(id, when) ScopeExit CONCAT(_scope_exit_, id)(when);\
    CONCAT(_scope_exit_, id) = 
#define SCOPE_EXIT SCOPE_HELPER(__COUNTER__, RUN_ALWAYS)
#define SCOPE_FAILURE SCOPE_HELPER(__COUNTER__, RUN_ON_FAILURE)
#define SCOPE_SUCCESS SCOPE_HELPER(__COUNTER__, RUN_ON_SUCCESS)



It creates a class, and 3 simplifying macros for you:
SCOPE_EXIT Creates a lambda that will be run any time the scope exits.
SCOPE_SUCCESS Run this lambda only when the scope exits without any exceptions in flight.
SCOPE_FAILURE Run this lambda only when the scope exits with exceptions in flight.

This is nice because you can have code that sets up things, calls functions, and has inline, easier to read, teardown code in a bad case:
//... pseudocode
File f = open("SomeFile", "rw");
SCOPE_EXIT [&f]() { f.close(); };
string contents = readFile(f);
string reversedContents = reverseString(contents);
if (reversedContents == contents)
{
    return true;
}
f.write(reversedContents);
return false;
//...



Now, that was a bit of a simple example, and didn't actually allow me to use one of the SCOPE_FAILURE or SCOPE_SUCCESS macros as I had wanted, but you get the idea.

While I was browsing I also tripped over this video that actually talks about this same thing, and goes through an implementation and how useful it can be at making code more legible. He is a bit long winded, but it is still a good watch.

If you add this with move semantics, you start getting easier and easier transactional programming and legible program flow (at least I think so). I will talk about move semantics at some point in the future, but for the time being I thought I would bring these up for discussion.

Do you use SCOPE_ macros already? Have you found them useful? Discuss.

Is This A Good Question/Topic? 3
  • +

Replies To: More advanced uses for scope guards

#2 Xupicor  Icon User is offline

  • Nasal Demon
  • member icon

Reputation: 456
  • View blog
  • Posts: 1,179
  • Joined: 31-May 11

Re: More advanced uses for scope guards

Posted 14 December 2015 - 07:16 AM

That's interesting.
I'll get to the video a bit later in the day, in the meantime - would it be to much to ask for a more involved example of, say, SCOPE_FAILURE? In your example, specifically, what if open() throws?

Also, not that it matters much since the support as far as I can tell is wide - but __COUNTER__ surprisingly isn't standard, right?

This post has been edited by Xupicor: 14 December 2015 - 07:22 AM

Was This Post Helpful? 0
  • +
  • -

#3 BetaWar  Icon User is offline

  • #include "soul.h"
  • member icon

Reputation: 1490
  • View blog
  • Posts: 8,217
  • Joined: 07-September 06

Re: More advanced uses for scope guards

Posted 14 December 2015 - 10:28 AM

Sure, here is a slightly different quick example with SCOPE_FAILURE:
uint8_t* readBuffer(std::string filename, std::size_t numBytes)
{
  uint8_t* buffer = new uint8_t[numBytes]; // if new throws here, we will just return, there won't be any memory allocated
  SCOPE_FAILURE [&buffer]() {
    delete [] buffer;
  };
  File f = open(filename.c_str(), "r");
  f.read(static_cast<char*>(buffer), numBytes);
  f.close();
  return buffer;
}


In this case, if new throws, then we don't have enough memory and shouldn't have been given any. Then we add out fail guard and attempt to open the file. If the file open fails (or the subsequent read, if for instance the file handle gets borked), we free the buffer on the way out of the scope.
NOTE - untested code.

Also you are correct in stating that __COUNTER__ is non-standard. Though it appears to be implemented in most places I have seen. We could change that up a bit to define __COUNTER__ to be __LINE__ if it doesn't exist to make sure it is a little more portable.
Was This Post Helpful? 1
  • +
  • -

#4 Skydiver  Icon User is offline

  • Code herder
  • member icon

Reputation: 5922
  • View blog
  • Posts: 20,248
  • Joined: 05-May 12

Re: More advanced uses for scope guards

Posted 15 December 2015 - 08:49 AM

I know those were strawmen examples above, but in production code, wouldn't you use the C++ streams and smart pointers and end up not needing the scope failure macros anyway?
Was This Post Helpful? 0
  • +
  • -

#5 BetaWar  Icon User is offline

  • #include "soul.h"
  • member icon

Reputation: 1490
  • View blog
  • Posts: 8,217
  • Joined: 07-September 06

Re: More advanced uses for scope guards

Posted 15 December 2015 - 09:43 AM

Certainly, not every solution is one that makes sense to use these on, and there are some constructs that are in place to help you avoid these things. I could see it being used to alert someone that something is going horribly wrong though, either logging a message that you had an issue, or alerting a server somewhere that you will be shutting down the connection soon, or some other higher level task.

The examples I gave above were literally just the first things that came to mind.

There are some situations that these will be useful, and some that you can do it better with pre-existing constructs. As long as you don't decide that this is the one hammer to rule them all things should work out pretty well :)
Was This Post Helpful? 0
  • +
  • -

#6 jjl  Icon User is offline

  • Engineer
  • member icon

Reputation: 1270
  • View blog
  • Posts: 4,997
  • Joined: 09-June 09

Re: More advanced uses for scope guards

Posted 30 December 2015 - 03:26 PM

Nice trick to get around messy exception handling. I'm not very familiar with C++11, how would this behave if there is already a queued uncaught exception that has yet to be handled? Or is std::uncaught_exception scoped per function.
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1