TrueJournals

Let the Language Work for You – Part 1: C++ Destructors

by on Oct.10, 2013, under programming

I recently came across a programming design pattern I hadn’t thought of, and that inspired me to write at least one blog post. So, I introduce this post which may or may not start a series of posts, entitled “Letting the Language Work for You”.

The idea behind this series is this: programming languages have become very advanced, possibly more than most people realize. There are many things you may be doing now in your programs that are simply extra work that you don’t really need to do. Instead, you should let the language do the work for you. These posts will (hopefully) help you take full advantage of your programming language to avoid extra work, and hopefully keep out some bugs.

For part 1, let’s look at destructors in C++. Before we get started, I’ll note that this post is very much inspired by Boost’s ScopedLock pattern. If you know where this is headed just by reading that, I’ll understand if you decide you don’t need to read what I have to say. Otherwise, if you use an object oriented language, I’d encourage you to keep going.

Recently, I was working on a semaphore locking system for a program at work. We had a hardware resource we were interacting with, and needed to ensure that only one thread was using that resource at a time. Luckily, there was already a support library which implemented a system level semaphore, so I had very little work to do. On top of that, we already had a class to interact with the hardware, so my work was almost done: just add a lock() and unlock() function. Simple!

However, I ran into a maintenance problem. Some of our functions that called lock() had multiple return points, all of which would need an unlock() before them. What if I forgot to unlock()? Well, we’d run into a deadlock situation for the hardware resource. That’s no good. There had to be some easier way to ensure that unlock() is called before a function returns, other than just putting in a bunch of unlock() calls. That’s when I found Boost’s ScopedLock.

As it turns out, there’s a very, very simple way to ensure that unlock() is called by taking an advantage of a promise C++ makes to you, the programmer:

If you allocate an object on the stack, its destructor will (almost always) be called when execution leaves the scope of that object.

So, let’s say we have some code like this:

int hardwareAction()
{
    if(hardwareCheck1() != 0)
    {
        return -1;
    }
    if(hardwareCheck2() != 0)
    {
        return -2;
    }
    if(hardwareCheck3() != 0)
    {
        return -3;
    }
    return 0;
}

The “simple” way to implement a lock would be this:

int hardwareAction()
{
    int lock = lock();
    if(hardwareCheck1() != 0)
    {
        unlock(lock);
        return -1;
    }
    if(hardwareCheck2() != 0)
    {
        unlock(lock);
        return -2;
    }
    if(hardwareCheck3() != 0)
    {
        unlock(lock);
        return -3;
    }
    unlock(lock);
    return 0;
}

However, there’s a few problems here. What if one of our “hardwareCheck” functions throws an exception? Unless we throw a try/catch around each call (or all three), we can’t do anything. What if we wanted to throw an exception? We would have to make sure to unlock before the exception. What if we missed one of the return statements? Deadlock.

Instead of tediously calling unlock() every time we need to release our lock, let’s make a new object, called “ScopedLock”:

class ScopedLock
{
private:
    int lock;
public:
    ScopedLock() : lock(lock()) { }
    ~ScopedLock() { if(locked()) unlock(lock); }
    bool locked() { return lock > -1; }
}

Note that this is probably the simplest implementation of a ScopedLock you could come up with. You may want some extra functionality in there, such as the ability to lock and unlock any time you want.

Now, what does our code look like with the scoped lock?

int hardwareAction()
{
    ScopedLock lock();
    if(hardwareCheck1() != 0)
    {
        return -1;
    }
    if(hardwareCheck2() != 0)
    {
        return -2;
    }
    if(hardwareCheck3() != 0)
    {
        return -3;
    }
    return 0;
}

Notice how we’re still able to lock the resource in only one line of code. But now… there’s no unlock call! How does that work.

It’s stupidly simple. When we leave the scope of the “ScopedLock” object we created (in this case, by a return call), its destructor is called. The ScopedLock destructor makes the call to unlock the resource.

And… that’s it! We can easily add more return points to this function without worrying about calling unlock(). We can throw an exception and not worry about it. One of our hardware checks could throw an exception, and we don’t care! As soon as we leave the scope, the destructor is called, and the resource is unlocked.

Now, you may be thinking, “but wait… how do I control where in a function lock and unlock are called? I need to be able to do that due to some huge processing in my function!”

Let’s say your function looks like this:

void hardwareAction()
{
    crazyProcessingThatTakesALongTime1();
    hardwareCheck();
    crazyProcessingThatTakesALongTime2()
}

You clearly don’t want the “crazy processing” included in the locked section of code, because you could be holding up someone else from using the shared resource, and you simply don’t need to.

The solution is, again, pretty simple: create a new scope with curly braces. This same function becomes:

void hardwareAction()
{
    crazyProcessingThatTakesALongTime1();
    {
        ScopedLock lock();
        hardwareCheck();
    }
    crazyProcessingThatTakesALongTime2()
}

Tah-dah! The curly braces create a new, very small, scope for your lock variable. When the closing curly brace is reached, execution leaves scope, and the ScopedLock destructor is called!

So, the next time you find yourself writing the same line of code before every “return” or “throw” in your code, consider creating a small “scope” class. Let the language work for you. Use C++ destructors to your advantage. Make them do work so you don’t have to.

:, ,

Leave a Reply

Looking for something?

Use the form below to search the site:

Still not finding what you're looking for? Drop a comment on a post or contact us so we can take care of it!

Visit our friends!

A few highly recommended friends...