Reputation: 1339
I'd like to wrap all usages of a class instance with a mutex. Today I have
std::map<int, std::shared_ptr<MyClass>> classes;
and functions to find and return instances, like:
std::shared_ptr<MyClass> GetClass(int i);
I'd like to ensure that GetClass() can only retrieve an instance if someone else hasn't already retrieved it, with some RAII mechanism. Usage would be like:
void CallingFunction()
{
auto c = GetClass(i); // mutex for class id 'i' is acquired here
// some calls to class
c.SomeFunction();
} // mutex is released here when 'c' goes out of scope
With the mutex acquired by CallingFunction() other threads that wanted to access the same class instance would block on their calls to GetClass().
I've been looking at a few ways of doing it, such as with a wrapper class like:
class ClassContainer
{
public:
std::shared_ptr<Class> c;
std::mutex m;
};
Where I'd modify GetClass() to be:
ClassContainer GetClass(int i);
But I'm having trouble figuring out both where the std::mutex should be kept, I tried initially storing it in the map before moving to using a container class like:
std::map<int, std::pair<std::mutex, std::shared_ptr<MyClass<>>> classes;
but that wasn't working well, now with the ClassContainer how to have ClassContainer lock the std::mutex like std::lock_guard<> when the caller acquires one via a call to GetClass().
Upvotes: 1
Views: 1047
Reputation: 44278
I've been looking at a few ways of doing it, such as with a wrapper class like:
Yes this is proper way to do it and you are close, but you cannot keep mutex
itself in this class, only locker. And std::unique_lock
is a proper type for that as it has necessary move ctor etc. I would make fields private though and create necessary accessors:
class ClassContainer
{
std::shared_ptr<Class> c;
std::uniqe_lock<mutex> lock;
public:
ClassContainer( std::pair<std::mutex,std::shared_ptr<Class>> &p ) :
c( p.second ),
lock( p.first )
{
}
Class * operator->()const { return c.get(); }
Class & operator*() const { return *c; }
};
then usage is simple:
void CallingFunction()
{
auto c = GetClass(i); // mutex for class id 'i' is acquired here
// some calls to class
c->SomeFunction();
// or even
GetClass(i)->SomeFunction();
}
Upvotes: 2
Reputation: 218098
It is Class
which should hold the mutex
, something like:
class Class
{
public:
// Your methods...
std::mutex& GetMutex() { return m; }
private:
std::mutex m;
};
class ClassContainer
{
public:
ClassContainer(std::shared_ptr<Class> c) :
c(std::move(c)),
l(this->c->GetMutex())
{}
ClassContainer(const ClassContainer&) = delete;
ClassContainer(ClassContainer&&) = delete;
ClassContainer& operator =(const ClassContainer&) = default;
ClassContainer& operator =(ClassContainer&&) = default;
// For transparent pointer like access to Class.
decltype(auto) operator -> () const { return c; }
decltype(auto) operator -> () { return c; }
const Class& operator*() const { return *c; }
Class& operator*() { return *c; }
private:
std::shared_ptr<Class> c;
std::lock_guard<std::mutex> l;
};
ClassContainer GetClass(int i)
{
auto c = std::make_shared<Class>();
return {c}; // syntax which avoids copy/move contructor.
}
and finally usage:
auto&& cc = GetClass(42); // `auto&&` or `const&` pre-C++17, simple auto possible in C++17
cc->ClassMethod();
Upvotes: 1
Reputation: 62603
Accidentally, I did something extremely similar recently (only I returned references to objects instead of shared_ptr
. The code worked like following:
struct locked_queue {
locked_queue(locked_queue&& ) = default;
mutable std::unique_lock<decltype(queue::mutex)> lock;
const queue::q_impl_t& queue; // std::deque
};
And here is how it would be used:
locked_queue ClassX::get_queue(...) {
return {std::unique_lock<decltype(mutex)>{mutex}, queue_impl};
}
Upvotes: 0