Try/Finally vs. RAII

Developing Find As You Type meant doing something I hadn't done in a while: pure C++ development. I'm also using C++ for some other projects now so I'm really getting back into the unmanaged way of thinking.

As a young whippersnapper I first learned to program in GW-BASIC, which was really easy in terms of memory management: there was none. There were no pointers, and all variables were global. In the early nineties I moved on to Visual Basic which, as long as you know the caveats of reference counting (e.g. the circular reference problem) is also not one of the biggest challenges in that regard. Then of course I started to learn C/C++ and got to deal with the wonders of malloc/free and new/delete.

One of the difficulties here is making sure that no matter what happens your resources get released, especially when using exceptions. How to make sure delete is called even when an exception occurs? And it's not just pointers; things like CloseFile or LeaveCriticalSection are all things that you want to regardless of how and where you exit the function. Of course there is a perfectly elegant way to deal with this, which I will come to later.

When .Net came out I learned about try/finally (which Java also has, but I hadn't really used Java at that time) and wondered why C++ didn't have this. The finally block is always executed. It doesn't matter if the try block was left because of control flow reaching the end of the block, because of a return statement, or an exception thrown; it would be executed. Back then I thought that would be a really useful thing to have in C++.

What I realized later is that the reason C++ doesn't have (or need) try/finally is because it doesn't have the problem try/finally was designed to solve: the lack of deterministic finalization. Since .Net and Java are garbage collected, there's no telling when finalizers will run so they need try/finally to be able to make sure things are cleaned up in a timely fashion. C#'s (and VB2005's) "using" statement is an example where try/finally is used (under the hood) for precisely this purpose.

C++ however does have deterministic finalization: a class variable's destructor will always run when it goes out of scope, and that too will happen for both exceptions and regular return. So the answer to the problem is that whenever you need to make sure something is cleaned up you simply create a wrapper class that does that finalization in its destructor. Of course I wasn't the first to think of that, and this is in fact a well known pattern called Resource Acquisition Is Initialization (RAII for short). C++ even provides one such wrapper class for automatic management of pointers: std::auto_ptr.

The reason I write about this now is because Find As You Type 1.2 is the first project where I really got to use this. For example I used the following two classes to deal with critical sections:

class CriticalSectionLock
{
public:
    CriticalSectionLock(LPCRITICAL_SECTION section) : _section(section)
    {
        if( _section != NULL )
            EnterCriticalSection(_section);
    }

    CriticalSectionLock(const CriticalSectionLock &right) : _section(right._section)
    {
        if( _section != NULL )
            EnterCriticalSection(_section);
    }

    CriticalSectionLock& operator=(const CriticalSectionLock &right)
    {
        if( _section != NULL )
            LeaveCriticalSection(_section);
        _section = right._section;
        if( _section != NULL )
            EnterCriticalSection(_section);
        return *this;
    }

    ~CriticalSectionLock()
    {
        if( _section != NULL )
            LeaveCriticalSection(_section);
    }

    void Leave()
    {
        if( _section != NULL )
        {
            LeaveCriticalSection(_section);
            _section = NULL;
        }
    }
private:
    LPCRITICAL_SECTION _section;
};

class CriticalSection
{
public:
    CriticalSection()
    {
        InitializeCriticalSection(&_section);
    }

    ~CriticalSection()
    {
        DeleteCriticalSection(&_section);
    }

    CriticalSectionLock Enter()
    {
        return CriticalSectionLock(&_section);
    }
private:
    CRITICAL_SECTION _section;
};

This enabled me to do two things: by having the CriticalSection class manage the initialization and freeing of a CRITICAL_SECTION, I could simply create static lifetime variables of that class which meant I no longer had to write code in DllMain to call Initialize/DeleteCriticalSection. It also enabled me to write code like this whenever I needed to enter a critical section:

CriticalSection _section; // global variable managing the lifetime of the critical section itself.

void SomeFunction()
{
    CriticalSectionLock lock = _section.Enter();

    // Do work here.
}

When the function exits, the CriticalSectionLock destructor will run, causing LeaveCriticalSection to be called. This happens even if the code doing the work throws an exception. If I want to leave the section before function exits I can simply call lock.Leave() myself.

So which is better? Try/finally certainly has the merit that you don't need to create a class for every little thing that needs this. The RAII approach feels cleaner to me. I don't know.

What is interesting is that C++/CLI not only brings try/finally to C++, it also brings RAII to .Net. Normally when you want to create a variable of a managed class type in C++/CLI you would use e.g. StreamReader ^reader = gcnew StreamReader(L"test.txt"); and then you'd have to call Dispose on it which is possible to ensure with try/finally in C++/CLI but unfortunately you don't get the convenient using syntax from C#.

But the nice thing is that you can also create the variable like this: StreamReader reader(L"test.txt");. That's using the syntax for stack variables. Of course, you don't actually get a stack variable; it's still a reference type, created on the managed heap. But you do get the semantics of a stack variable; if the class implements IDisposable, Dispose will be called automatically when the variable goes out of scope. And that is very nifty indeed.

Categories: Programming
Posted on: 2007-02-22 11:01 UTC.

Comments

No comments here...

Add comment

Comments are closed for this post. Sorry.

Latest posts

Categories

Archive

Syndication

RSS Subscribe