2

I'm trying to create a wrapper class W in C++, which is constructed with a pointer to a generic object OBJ.

When you call one of OBJ methods through W, W (containing a condition variable cv) issues a cv.wait() before calling OBJ method and a cv.notify() when OBJ method has finished.

I've been able to do it with inheritance for a specific class, but would like a generic approach like the one described above.

This is the inheritance approach:

struct A
{
    virtual void foo(int i) { bar = i; };
    int bar;
};

struct B : public A
{
    void foo2(int i)
    {
        cv.wait(lck);
        this->foo(i);
        cv.notify_one();
    }
    std::condition_variable cv;
    std::unique_lock<std::mutex> lck;
};

I would like something in like:

template<class T>
struct C
{
    C(T *t) : t(t) {}

    T *operator->()
    {
        cv.wait(lck);
        return t;
        // notify_one when function has completed
    }

    T *t;
    std::condition_variable cv;
    std::unique_lock<std::mutex> lck;
};

I found an answer using only locks (but that is not really a monitor): https://stackoverflow.com/a/48408987

sornbro
  • 243
  • 3
  • 12

2 Answers2

4

Here is a working example:

#include <iostream>
#include <mutex>

template<class T>
class Wrapper {
    std::mutex m;
    T* p;

public:
    Wrapper(T& t) : p(&t) {}

    class Proxy {
        std::unique_lock<std::mutex> lock;
        T* p;

    public:
        Proxy(std::mutex& m, T* p)
            : lock(m)
            , p(p)
        {
            // Locked the mutex.
            std::cout << __PRETTY_FUNCTION__ << '\n';
        }

        Proxy(Proxy&& b) = default;

        ~Proxy() {
            std::cout << __PRETTY_FUNCTION__ << '\n';
            // Unlocked the mutex.
        }

        T* operator->() const { return p; }
    };

    Proxy operator->() { return Proxy(m, p); }
};

struct A {
    void f() { std::cout << __PRETTY_FUNCTION__ << '\n'; }
};

int main() {
    A a;
    Wrapper<A> w(a);
    w->f();
}

Outputs:

Wrapper<T>::Proxy::Proxy(std::mutex&, T*) [with T = A]
void A::f()
Wrapper<T>::Proxy::~Proxy() [with T = A]

See Wrapping C++ Member Function Calls by Bjarne Stroustrup for full details.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
  • 1
    I like this a lot better than mine. +1 – NathanOliver Sep 09 '19 at 16:17
  • I need to use condition variables to put threads trying to access the object's method to sleep when there is already another thread accessing that object's methods. This is almost the same as the link in the original post. – sornbro Sep 09 '19 at 16:20
  • @sornbro That is what locking mutex does - only one thread can lock the mutex, others get blocked. You don't need conditional variables here. – Maxim Egorushkin Sep 09 '19 at 16:21
  • @MaximEgorushkin but they don't get put to sleep. – sornbro Sep 09 '19 at 16:22
  • 1
    @sornbro They do. [Learn how mutex works](https://en.cppreference.com/w/cpp/thread/mutex). Don't confuse a mutex with a spin-lock. – Maxim Egorushkin Sep 09 '19 at 16:22
  • 1
    @sornbro `condition_variable` was completely out of place in your code. Also, generally I do not recommend this style of mutex locking, it is suitable only in very specific scenarios. You may easily get a nasty bug if you assume that there were no outside interference between operations. – ALX23z Sep 09 '19 at 16:28
  • 1
    @sornbro If a mutex is already locked, locking mutex _blocks_ until the mutex becomes available. _Blocking_ means that the thread is de-scheduled off the CPU. See more info about mutexes here: http://man7.org/linux/man-pages/man3/pthread_mutex_lock.3p.html – Maxim Egorushkin Sep 09 '19 at 16:33
1

It's not great, but what you can do is have a single function the user calls from the wrapper and you supply a lambda that calls the function they actually want to use. This makes it more verbose to use the wrapper but it allows you to wrap any type without trying to figure out how to forward all of its functions (something that really needs refelection to do generically). That would give you a wrapper like

template<class T>
struct C
{
    C(T *t) : t(t) {}

    template<typename Func>
    void call(Func func)
    {
        wait operation
        func(t);
        notify operation
    }

    T *t;
};

and then you would use it like

Foo_ptr foo =  ...;
C wrapper(foo);
wrapper.call([](auto ptr){ return ptr->func_I_want_to_call(arguments); });

and that will call func_I_want_to_call on t from the wrapper with the supplied arguments.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402