Making a C++ class a Monitor (in the concurrent sense)

I want to ensure that only one thread at a time can run a method of my C++ class. In other words, make the class behave like a Monitor.

Is there a pattern, templatized way to do this, or some Boost class I can use? Because my only idea so far is adding a Critical Section member, and acquire it at the beginning of each method and release it at the end (using RAII, of course). But that seems very redundant, and I can't reuse it for some other class.


You can achieve this with some judicious use of operator-> and modern c++ which gives for much cleaner syntax than the previously accepted answer:

template<class T>
class monitor
{
public:
    template<typename ...Args>
    monitor(Args&&... args) : m_cl(std::forward<Args>(args)...){}

    struct monitor_helper
    {
        monitor_helper(monitor* mon) : m_mon(mon), m_ul(mon->m_lock) {}
        T* operator->() { return &m_mon->m_cl;}
        monitor* m_mon;
        std::unique_lock<std::mutex> m_ul;
    };

    monitor_helper operator->() { return monitor_helper(this); }
    monitor_helper ManuallyLock() { return monitor_helper(this); }
    T& GetThreadUnsafeAccess() { return m_cl; }

private:
    T           m_cl;
    std::mutex  m_lock;
};

The idea is that you use the arrow operator to access the underlying object, but that returns a helper object which locks and then unlocks the mutex around your function call. Then through the magic of the language repeatedly applying operator-> you get a reference to the underlying object.

Usage:

monitor<std::vector<int>> threadSafeVector {5};

threadSafeVector->push_back(0);
threadSafeVector->push_back(1);
threadSafeVector->push_back(2);

// Create a bunch of threads that hammer the vector
std::vector<std::thread> threads;
for(int i=0; i<16; ++i)
{
    threads.push_back(std::thread([&]()
    {
        for(int i=0; i<1024; ++i)
        {
            threadSafeVector->push_back(i);
        }
    }));
}

// You can explicitely take a lock then call multiple functions
// without the overhead of a relock each time. The 'lock handle'
// destructor will unlock the lock correctly. This is necessary
// if you want a chain of logically connected operations 
{
    auto lockedHandle = threadSafeVector.ManuallyLock();
    if(!lockedHandle->empty())
    {
        lockedHandle->pop_back();
        lockedHandle->push_back(-3);
    }
}

for(auto& t : threads)
{
    t.join();
}

// And finally access the underlying object in a raw fashion without a lock
// Use with Caution!

std::vector<int>& rawVector = threadSafeVector.GetThreadUnsafeAccess();
rawVector.push_back(555);

// Should be 16393 (5+3+16*1024+1)
std::cout << threadSafeVector->size() << std::endl;

First make generic monitor class. With power of C++11 you can do it as simple as this:

template <class F>
struct FunctionType;
template <class R, class Object, class... Args>
struct FunctionType<R (Object::*)(Args...)> {
  typedef R return_type;
};
template <class R, class Object, class... Args>
struct FunctionType<R (Object::*)(Args...) const> {
  typedef R return_type;
};

template <class Object_>
class Monitor {
public:
   typedef Object_ object_type;
   template <class F, class... Args >
   typename FunctionType<F>::return_type operation(const F& f, Args... args)
   {
       critical_section cs;
       return (object.*f)(args...);
   }
   template <class F, class... Args >
   typename FunctionType<F>::return_type operation(const F& f, Args... args) const
   {
       critical_section cs;
       return (object.*f)(args...);
   }
private:
  object_type object;
  class critical_section {};
};

Of course critical_section implementation is up to you. I recommend POSIX or some BOOST.

It is ready to use right now:

Monitor<std::vector<int> > v;
v.operation((void (std::vector<int>::*)(const int&)) &std::vector<int>::push_back, 1);
v.operation((void (std::vector<int>::*)(const int&)) &std::vector<int>::push_back, 2);
size = v.operation(&std::vector<int>::size);
std::cout << size << std::endl;

As you can see sometimes you'll need to explicitly state which member function you want to call - std::vector<> has more than one push_back...


For compilers which still do not support variadic template - the solution without it below - I have time for up to two arguments - it is very inconvenient - if required - add function with more arguments:

template <class F>
struct FunctionType;
template <class R, class Object>
struct FunctionType<R (Object::*)()> {
  typedef R return_type;
};
template <class R, class Object>
struct FunctionType<R (Object::*)() const> {
  typedef R return_type;
};
template <class R, class Object, class Arg1>
struct FunctionType<R (Object::*)(Arg1)> {
  typedef R return_type;
};
template <class R, class Object, class Arg1>
struct FunctionType<R (Object::*)(Arg1) const> {
  typedef R return_type;
};
template <class R, class Object, class Arg1, class Arg2>
struct FunctionType<R (Object::*)(Arg1,Arg2)> {
  typedef R return_type;
};
template <class R, class Object, class Arg1, class Arg2>
struct FunctionType<R (Object::*)(Arg1,Arg2) const> {
  typedef R return_type;
};

template <class Object_>
class Monitor {
public:
   typedef Object_ object_type;
   template <class F>
   typename FunctionType<F>::return_type operation(const F& f)
   {
       critical_section cs;
       return (object.*f)();
   }
   template <class F>
   typename FunctionType<F>::return_type operation(const F& f) const
   {
       critical_section cs;
       return (object.*f)();
   }
   template <class F, class Arg1>
   typename FunctionType<F>::return_type operation(const F& f, Arg1 arg1)
   {
       critical_section cs;
       return (object.*f)(arg1);
   }
   template <class F, class Arg1>
   typename FunctionType<F>::return_type operation(const F& f, Arg1 arg1) const
   {
       critical_section cs;
       return (object.*f)(arg1);
   }
   template <class F, class Arg1, class Arg2>
   typename FunctionType<F>::return_type operation(const F& f, Arg1 arg1, Arg2 arg2)
   {
       critical_section cs;
       return (object.*f)(arg1, arg2);
   }
   template <class F, class Arg1, class Arg2>
   typename FunctionType<F>::return_type operation(const F& f, Arg1 arg1, Arg2 arg2) const
   {
       critical_section cs;
       return (object.*f)(arg1, arg2);
   }
private:
  object_type object;
  class critical_section {};
};