views:

93

answers:

3

Consider the following C++ member function:

 size_t size() const
 {
    boost::lock_guard<boost::mutex> lock(m_mutex);
    return m_size;
 }

The intent here is not to synchronize access to the private member variable m_size, but just to make sure that the caller receives a valid value for m_size. The goal is to prevent the function from returning m_size at the same time that some other thread is modifying m_size.

But is there any potential race-condition involved in calling this function? I'm not sure if the RAII style lock here is adequate to protect against a race condition. Suppose the lock's destructor is called before the return value of the function is pushed onto the stack?

Would I need to do something like the following to guarantee thread-safety?

 size_t size() const
 {
    size_t ret;

    {
      boost::lock_guard<boost::mutex> lock(m_mutex);
      ret = m_size;
    }

    return ret;
 }
A: 

Your initial code is fine - the destructor will be called after the return value has been stored. This is the very principle RAII operates on!

Nicholas M T Elliott
Do you have any sources for that?
Georg Fritzsche
+2  A: 

Your first variant is safe, however you can't rely on this returned value to be consistent for any period of time. I mean for example don't use that returned value in a for loop to iterate over each element, because the real size could change right after the return.

Basically you can think of it like this: a copy is needed of the return value, otherwise the destructor would be called hence possibly corrupting whatever the return value was before it was returned.

The destructor is called after the return statement. Take this equivalent example:

#include <assert.h>

class A
{
public:
    ~A()
    {
        x = 10;
    }
    int x;
};

int test()
{
    A a;
    a.x = 5;
    return a.x;
}

int main(int argc, char* argv[])
{
    int x = test();
    assert(x == 5);
    return 0;
}
Brian R. Bondy
After thinking about it, I see that this is the only way the behaviour could work. Every `return expr;` implicitly copies the value of `expr` into a temporary, and any object in scope can be referred to in `expr` so they must still be live at the point when the copy is made. (Optimisations could remove the copy of course, but the behaviour must still be "as if" it was there.)
j_random_hacker
+5  A: 

Both of your example constructs will do what you're looking for. The following information from the standard supports the behavior you're looking for (even in your 1st example):

12.4/10 Destructors:

Destructors are invoked implicitly ... for a constructed object with automatic storage duration (3.7.2) when the block in which the object is created exits.

And, 6.6/2 Jump statements (of which return is one):

On exit from a scope (however accomplished), destructors (12.4) are called for all constructed objects with automatic storage duration (3.7.2) (named objects or temporaries) that are declared in that scope, in the reverse order of their declaration. Transfer out of a loop, out of a block, or back past an initialized variable with automatic storage duration involves the destruction of variables with automatic storage duration that are in scope at the point transferred from but not at the point transferred to.

So at the point of the return the lock variable is in scope and therefore the dtor has not been called. Once the return has been executed, the dtor for the lock variable is called (thus releasing the lock).

Michael Burr