In the classic problem of transferring money from one bank account to another, the accepted solution (I believe) is to associate a mutex with each bank account, then lock both b
I prefer to use a non-intrusive wrapper class instead of polluting the original object with a mutex and locking it on each method call. This wrapper class (which I named Protected
) contains the user object as a private variable. Protected
grants friendship to another class called Locker
. The locker takes the wrapper as its constructor argument and provides public accessor methods to the user object. The locker also keeps the wrapper's mutex locked during its lifetime. So the locker's lifetime defines a scope where the original object can be accessed in a safe way.
The Protected
can implement operator->
to enable quickly calling a single method.
Working example:
#include
#include
template
struct Locker;
template
struct Protected
{
template
Protected(Args && ...args) :
obj_(std::forward(args)...)
{
}
Locker operator->() const;
Locker operator->();
private:
friend class Locker;
friend class Locker;
mutable std::mutex mtx_;
T obj_;
};
template
struct Locker
{
Locker(Protected & p) :
lock_(p.mtx_),
obj_(p.obj_)
{
std::cout << "LOCK" << std::endl;
}
Locker(Locker && rhs) = default;
~Locker()
{
std::cout << "UNLOCK\n" << std::endl;
}
const T& get() const { return obj_; }
T& get() { return obj_; }
const T* operator->() const { return &get(); }
T* operator->() { return &get(); }
private:
std::unique_lock lock_;
T & obj_;
};
template
struct Locker
{
Locker(const Protected & p) :
lock_(p.mtx_),
obj_(p.obj_)
{
std::cout << "LOCK (const)" << std::endl;
}
Locker(Locker && rhs) = default;
~Locker()
{
std::cout << "UNLOCK (const)\n" << std::endl;
}
const T& get() const { return obj_; }
const T* operator->() const { return &get(); }
private:
std::unique_lock lock_;
const T & obj_;
};
template
Locker Protected::operator->()
{
return Locker(const_cast&>(*this));
}
template
Locker Protected::operator->() const
{
return Locker(const_cast&>(*this));
}
struct Foo
{
void bar() { std::cout << "Foo::bar()" << std::endl; }
void car() const { std::cout << "Foo::car() const" << std::endl; }
};
int main()
{
Protected foo;
// Using Locker for rw access
{
Locker locker(foo);
Foo & foo = locker.get();
foo.bar();
foo.car();
}
// Using Locker for const access
{
Locker locker(foo);
const Foo & foo = locker.get();
foo.car();
}
// Single actions can be performed quickly with operator->
foo->bar();
foo->car();
}
Which generates this output:
LOCK
Foo::bar()
Foo::car() const
UNLOCK
LOCK (const)
Foo::car() const
UNLOCK (const)
LOCK
Foo::bar()
UNLOCK
LOCK
Foo::car() const
UNLOCK
Test with online compiler.
Update: fixed const correctness.
PS: There's also an asynchronous variant.