Under The Hood
We've seen how to use SynchronizedValue<T> to ensure that the right mutex is always locked when accessing an object; now it's time to look under the hood and see how it works.
It really is very simple: at its heart, a SynchronizedValue<T> is just a T object and a std::mutex. All the work is done by the proxy objects returned from the operators.
template<typename T>
class SynchronizedValue
{
T data;
std::mutex m;
public:
DerefValue operator*();
Updater operator->();
Updater update();
};
Basic Pointer Operations
The basic pointer dereference operator returns an instance of the private nested class SynchronizedValue<T>::DerefValue:
DerefValue operator*()
{
return DerefValue(*this);
}
This class holds a std::unique_lock<std::mutex> which manages the lock and a reference to the protected T object. The lock is acquired when the object is constructed by the call to operator*, and released when the object is destroyed. DerefValue has a simple conversion operator to T which allows retrieval of the protected value, and also an assignment operator which allows the value to be set.
operator T()
{
return data;
}
DerefValue& operator=(T const& newVal)
{
data=newVal;
return *this;
}
The arrow operator is implemented similarly, except this time it returns an instance of SynchronizedValue<T>::Updater. Because SynchronizedValue<T>::Updater also has an implementation of the arrow operator, the chaining rules for this operator mean that you can access members of the protected object directly using the normal pointer syntax, and the temporary Updater object holds the lock for you. Just like DerefValue, the Updater object acquires the lock in its constructor and releases it in its destructor.
This is the same Updater object we get from the update() member function, so it has a plain pointer dereference operator that returns a reference to the protected object in addition to the arrow operator that returns the pointer:
T* operator->()
{
return &data;
}
T& operator*()
{
return data;
}
Wrapping it Up
I've found classes like SynchronizedValue<T> invaluable in many projects, as it provides a simple way of ensuring that the right mutex is always held when accessing protected data. It's not a panacea, but those cases where it doesn't work likely require more careful thought anyway.
You can download the source code here, or from http://www.stdthread.co.uk/syncvalue if you wish to try it out. As written, it relies on C++0x facilities, but it should be easy to substitute an alternative mutex implementation if desired.


