6

Before anything: I'm not a developer and I might not understand some of your messages, and as English is not my native language my question could be hard to understand.

Considering :

class MyVector  
{
    std::vector<command> vec;
    std::mutex vector_m;

public:
    void MVpush_back(command t)
    {
        this->vector_m.lock();
        this->vec.push_back(t);
        this->vector_m.unlock();
    }

};

command is a custom class (its content doesn't seem relevant here; copy constructor does exist).

Basically, as I have a lot of possible writer & readers, thus I want to force the use of the mutex to access to the vec parameter.

As I'll only use push_back(), erase() and find() I could redefine them, but I was wondering if there is a way not have to redefine all functions.

something like:

 <template> safe(*function name*<template>)
 {
   this->vector_m.lock();
   <template> retval = vec.*function name*<parameter>;
   this->vector_m.unlock();
   return retval;
 }

where the function to call is a kind of parameter...

I thought it could be done using std::initializer_list<type> but the type requirement is blocking.

Is there a way to do such a thing?

Rephrased question: is there a way to push a function with parameter(1) as parameter of a function(2) and make function(2) call function(1) ?

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
A.albin
  • 274
  • 2
  • 15
  • If you inherit from `std::vector` why do you have your internal vector inside of the class? And why don't you override the standard `push_back` instead of creating your own custom function which doesn't push back into itself but uses the extra vector? – Some programmer dude Aug 03 '17 at 09:29
  • Before inheriting from std vector perhaps read this https://stackoverflow.com/questions/4353203/thou-shalt-not-inherit-from-stdvector and this https://stackoverflow.com/questions/6806173/subclass-inherit-standard-containers/7110262#7110262. – systemcpro Aug 03 '17 at 09:29
  • I need to rephrase the question, I realized about that just after I posted – A.albin Aug 03 '17 at 09:33
  • I'd create a non-copyable class that holds the vector and a lock to the mutex; and unlocks it on destruction; anything that then wants to access the vector has to obtain this class first. Since only one instance of this class can exist at a time because of the lock in the constructor, you can be sure that only one thing is accessing the vector at a time – UKMonkey Aug 03 '17 at 09:33
  • 1
    I'd just use a lock guard to get the mutex – M.M Aug 03 '17 at 09:36
  • @UKMonkey seems interesting can you give more details or examples? – A.albin Aug 03 '17 at 09:38

4 Answers4

4

If you don't mind sacrificing the use of the member access operator (.), you can wrap all the vector operations neatly into lockable operations.

class MyVector {
  std::vector<command> vec;
  std::mutex vector_m;

  struct locker {
    MyVector& _ref;
    locker(MyVector& parent) : _ref(parent) {
      _ref.vector_m.lock();
    }
    ~locker() { _ref.vector_m.unlock(); }

    std::vector<command>* operator->() && { return &_ref.vec; }
  };

  public:
    locker operator->() { return {*this}; }
};

Now, every access to the underlying vector will lock and unlock the vector for the duration of the operation:

MyVector mv;
mv->push_back(/* ... */);
// This locks the mutex before doing the push back
// And unlocks it immediately after, even in the face of exceptions.

The magic is in operator-> acting in a transitive manner. It is applied to the return value of itself until a regular pointer is returned, which is then accessed as usual. But every temporary along the way is created and destroyed in LIFO order. So the temporary MyVector::locker object has a lifetime that is just the duration of the access more or less.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
0

Here's a quick not particularly fantastic version of the suggestion I made in the comments; not compiled or tested; just something so that you can get the idea.

template<class T>
class OverkillProtector {
private:
    T& d;
    std::unique_lock<std::mutex>lock ;

public:
    OverkillProtector(T& d_, std::mutex& m_) :
        d(d_),
        lock(m_)
    {}
    OverkillProtector(const OverkillProtector&) = delete;
    OverkillProtector& operator =(const OverkillProtector&) = delete;

    T& getValue() { return d; }
    const T& getValue() const { return d; }
};

Note that in the (default) desturctor, the unique lock will be destroyed, which will release the mutex. Note that the lifetime of this object is required to be less than that of the mutex or the data you're wrapping.

UKMonkey
  • 6,941
  • 3
  • 21
  • 30
  • Im sorry, can you provide more details/information ? I'll read related documentation as soon as possible, but I have to admit that I'm lost. – A.albin Aug 03 '17 at 09:52
  • Have a read of the docs, and let me know which bit in particular you're lost on. – UKMonkey Aug 03 '17 at 09:55
  • "Note that the lifetime of this object is required to be less than that of the mutex or the data you're wrapping." is the part I don't understand. the protected vector can't exist forever? if yes how to call it? and use the class? – A.albin Aug 03 '17 at 09:56
  • Maybe it's too complex; Why don't you just keep what you have; and replace the repeated 'lock' and 'unlock' calls with a single 'std::unique_lock' and move on - it's only a few functions :) – UKMonkey Aug 03 '17 at 09:59
  • I'm just wondering if there is a tool/way to do it out of curiosity more than anything. If you can could you provide an example on how to use it with the MyVector class? – A.albin Aug 03 '17 at 10:01
  • Ok I understood how to use it. But I think that it will not fit as I've to call it from various thread and the mutex is being destroyed every time? (or I totally miss-understood) – A.albin Aug 03 '17 at 10:14
  • You create it from any thread; and the *lock* is created every time - the mutex is not. The lock is something that calls mutex.lock() on create, and mutex.unlock on destroy – UKMonkey Aug 03 '17 at 10:15
  • So, if I got it right call is Protector> Pttr(base_vector, mtx); followed by Pttr.push_back("whatever"); is there a way to avoid to have to pass the mutex as argument? Idea will be to keep the Protector object as a global variable, if its do-able... – A.albin Aug 03 '17 at 10:20
  • Or if I do Protector Test(??,??); what should I give to init the Protector? – A.albin Aug 03 '17 at 10:27
  • Ok, sorry. I just understood. As there is no copy possible from getValue() its doing exactly what I was trying to do. Took me some time to understand by toying with it, but I got it know. – A.albin Aug 03 '17 at 10:37
0

A template approach might look like this:

class MyVector
{
    std::vector<command> vec;
    mutable std::mutex vector_m;
public:
    template <typename R, typename ... T, typename ... P>
    R safeCall(R (std::vector<command>::*f)(T ...), P&& ... p)
    {
        std::lock_guard<std::mutex> l(vector_m);
        return (vec.*f)(std::forward<P>(p)...);
    }
    template <typename R, typename ... T, typename ... P>
    R safeCall(R (std::vector<command>::*f)(T ...) const, P&&  ... p) const
    {
        std::lock_guard<std::mutex> l(vector_m);
        return (vec.*f)(std::forward<P>(p)...);
    }
};

void test()
{
    MyVector v;
    v.safeCall(&std::vector<int>::push_back, 7);
    MyVector const* vv = &v;
    int n = vv->safeCall(&std::vector<int>::operator[], 0);
}

Well, you safe the work of re-implementing the interface, but usage gets rather ugly – typedef for the vector type gets it minimally shorter, but still... A macro?

#define safe_call(V, R, F, ...) V R safeCall(&std::vector<int>::F, ## __VA_ARGS__)
safe_call(v, ., push_back, 7);
safe_call(vv, ->, operator[], 1);

Or a little shorter:

#define safe_call(V, F, ...) V safeCall(&std::vector<int>::F, ## __VA_ARGS__)
safe_call(v., push_back, 7);
safe_call(vv->, operator[], 1);

Well, I won't comment further, decide yourself...

In the end, you might bite the bullet and really duplicate the interface for the sake of more convenient usage afterwards - a helper template might facilitate the task, though:

class MyVector
{
    std::vector<command> vec;
    mutable std::mutex vector_m;

    template <typename R, typename ... T>
    R safeCall(R (std::vector<command>::*f)(T...), T... t)
    {
        std::lock_guard<std::mutex> l(vector_m);
        return (vec.*f)(t...);
    }
    // const variant, too
public:
    // ...
};

void MyVector::push_back(Command t)
{
    safeCall(&std::vector<Command>::push_back, t);
}
Aconcagua
  • 24,880
  • 4
  • 34
  • 59
  • This will require to re-implement all std::vector methods, question was about not having to do so. – A.albin Aug 03 '17 at 10:38
  • OK, now not duplicating the interface any more - but I don't really like it, so I left the original approach, too... – Aconcagua Aug 03 '17 at 11:50
0

You might do something like:

class MyVector  
{
    std::vector<command> vec;
    std::mutex vector_m;
public:
    template <typename F>
    decltype(auto) Do(F&& f)
    {
        std::unique_lock<std::mutex> lock{vector_m};
        return std::forward<F>(f)(vec);
    }
};

With usage similar to:

MyVector myVector;
command myCommand;

myVector.Do([&](auto& vec) { vec.push_back(myCommand); });
Jarod42
  • 203,559
  • 14
  • 181
  • 302