Boost logo

Boost :

Subject: [boost] Proposed new RAII Library
From: Andrew Sandoval (sandoval_at_[hidden])
Date: 2012-09-11 21:40:46


I'd like to propose a new Boost library for RAII wrapper classes that
I think could be standardized to help promote the use of RAII. Though
shared_ptr, scoped_ptr, etc. greatly enhance the use of RAII, I
propose two additional classes.

The first of these is RAIIFunction (I'm open to other names and coding
conventions). This one is very simple, but powerful. It's a small
wrapper (using composition) around std::tr1::function, that simply
calls the function on scope-exit, in the normal case. Unlike
Boost.ScopeExit, it uses no macros. Anything that can be assigned to
a std::tr1::function<AnyType> taking no parameters will work with it,
so lambdas are perfect -- and allow for all of the associated benefits
like capturing variables by reference, etc. It also works with
std::tr1::bind / boost::bind, with no placeholders.

//
// RAIIFunction<> Written by Andrew L. Sandoval 2011/2012
// Include guard here
#include <functional>

//
// An RAII class that works with std::tr1::bind and lambdas
// NOTE: making a const RAIIFunction prevents the use of Invoke()
// and Cancel() and operator=(fn) which is often appropriate
// NOTE: m_func copies the original function object, so that const and
// non-const refs can be passed in, etc.
template <typename TRet>
class RAIIFunction
{
private:
    std::tr1::function<TRet ()> m_func;
    bool m_bInvoke;

    RAIIFunction();
    RAIIFunction(const RAIIFunction &);
    void operator=(const RAIIFunction &);
public:
    RAIIFunction(const std::tr1::function<TRet ()> &fn) : m_func(fn),
        m_bInvoke(true)
    {
    }

    void operator=(const std::tr1::function<TRet ()> &fn)
    {
        if(m_bInvoke)
        {
            m_func();
        }
        m_bInvoke = true;
        m_func = fn;
    }

    ~RAIIFunction()
    {
        if(m_bInvoke)
        {
            m_func();
        }
    }

    void Cancel()
    {
        m_bInvoke = false;
    }

    TRet Invoke(bool bRelease = false)
    {
        m_bInvoke = !bRelease;
        return m_func();
    }
};

Examples:
const RAIIFunction<void> closeDB = ([pDB]()
{
    if(pDB)
    {
        sqlite3_close(pDB);
    }
});

//
// while example...
bool bContinue = true;
RECORD record = { };
while(bContinue)
{
    // Ensure that no matter how the loop wraps (continue or
fall-through) the next
    // record is grabbed or the loop terminates...
    const RAIIFunction<void> loopAdvance = ([&bContinue]()
    {
        bContinue = SomeCallThatGetsAnotherRecord(record);
    });

    // A bunch of record processing here ...
}

//
// A convoluted example that shows assignment, etc.
RAIIFunction<int> mb = std::tr1::bind(MessageBoxW,
    HWND_TOP,
    L"Present pop-up prompt again?",
    L"Test", MB_TOPMOST|MB_YESNO);
int iMB = mb.Invoke(true);
if(IDYES != iMB)
{
    mb = ([]() ->int
    {
        dprintf("Printing what the user didn't wanted popped up"
        " on scope-exit...\n");
        return 0;
    });

--
The other one I call just RAIIWrapper, and it works with static
deleter functions taking a single argument of the type being delete.
Since it includes a lot more code (much of which could probably be
replaced with type_traits code), I'll just show examples of usage
below to gauge interest.  I first used an earlier version of this in
an environment where the standard function required a goto ErrorExit
(like Microsoft example driver code in the WDK) to clean-up all of the
resources.  The original concern was that the c++ class would bloat
the deliverable size.  This class proved to be as tight as the goto
equivalent.  It uses one macro that I'll include below.  The class
takes 3 template parameters: 1) The type of the deleter function, 2)
the deleter function, and 3) a no-delete value that defaults to NULL.
//
// Simplify usage with a macro:
#define RAIIDeleter(deleterFunction) decltype(&deleterFunction), deleterFunction
// Example:
const RAIIWrapper<RAIIDeleter(CloseHandle), INVALID_HANDLE_VALUE>
hFile = CreateFile(pwzFileName,
    GENERIC_WRITE,
    FILE_SHARE_WRITE,
    NULL,
    CREATE_ALWAYS,
    FILE_ATTRIBUTE_NORMAL,
    NULL);
if(!hFile.IsValid())
{
    // handle error...
}
// Similar but using the default no-delete value (NULL):
const RAIIWrapper<RAIIDeleter(CloseHandle)> hEvent = CreateEvent(NULL,
FALSE, FALSE, NULL);
--
I've found that using RAII consistently can dramatically improve code
quality.  It forces the coder to think about resource lifetime at
inception rather than at exit points, etc.  Much of what I've shown
above can be done with scoped_ptr or shared_ptr, but these specialized
classes put the focus on RAII and resource management.
Please let me know your thoughts.  I'd love to see this become part of
Boost, and hopefully eventually part of the C++ standard library.
Thank You,
Andrew Sandoval

Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk