Reputation: 5521
Most of the times I see in the code some variant of this kind of implementation for a thread safe getter method:
class A
{
public:
inline Resource getResource() const
{
Lock lock(m_mutex);
return m_resource;
}
private:
Resource m_resource;
Mutex m_mutex;
};
Assuming that the class Resource can't be copied, or that the copy operation has a too high computational cost, is there a way in C++ to avoid the returning copy but still using a RAII style locking mechanism?
Upvotes: 4
Views: 1791
Reputation: 5680
I haven't tried it, but something like this should work:
#include <iostream>
#include <mutex>
using namespace std;
typedef std::mutex Mutex;
typedef std::unique_lock<Mutex> Lock;
struct Resource {
void doSomething() {printf("Resource::doSomething()\n"); }
};
template<typename MutexType, typename ResourceType>
class LockedResource
{
public:
LockedResource(MutexType& mutex, ResourceType& resource) : m_mutexLocker(mutex), m_pResource(&resource) {}
LockedResource(MutexType& mutex, ResourceType* resource) : m_mutexLocker(mutex), m_pResource(resource) {}
LockedResource(LockedResource&&) = default;
LockedResource(const LockedResource&) = delete;
LockedResource& operator=(const LockedResource&) = delete;
ResourceType* operator->()
{
return m_pResource;
}
private:
Lock m_mutexLocker;
ResourceType* m_pResource;
};
class A
{
public:
inline LockedResource<Mutex, Resource> getResource()
{
return LockedResource<Mutex, Resource>(m_mutex, &m_resource);
}
private:
Resource m_resource;
Mutex m_mutex;
};
int main()
{
A a;
{ //Lock scope for multiple calls
auto r = a.getResource();
r->doSomething();
r->doSomething();
// The next line will block forever as the lock is still in use
//auto dead = a.getResource();
} // r will be destroyed here and unlock
a.getResource()->doSomething();
return 0;
}
But be careful, as the lifetime of the accessed Resource depends on the lifetime of the owner (A
)
Example on Godbolt: Link
P1144 reduces the generated assembly quite nicely so that you can see where the lock is locked and unlocked.
Upvotes: 4
Reputation: 69912
another way to achieve the same thing. This is the implementation of a mutable version. the const accessor is just as trivial.
#include <iostream>
#include <mutex>
struct Resource
{
};
struct locked_resource_view
{
locked_resource_view(std::unique_lock<std::mutex> lck, Resource& r)
: _lock(std::move(lck))
, _resource(r)
{}
void unlock() {
_lock.unlock();
}
Resource& get() {
return _resource;
}
private:
std::unique_lock<std::mutex> _lock;
Resource& _resource;
};
class A
{
public:
inline locked_resource_view getResource()
{
return {
std::unique_lock<std::mutex>(m_mutex),
m_resource
};
}
private:
Resource m_resource;
mutable std::mutex m_mutex;
};
using namespace std;
auto main() -> int
{
A a;
auto r = a.getResource();
// do something with r.get()
return 0;
}
Upvotes: 4
Reputation: 6139
How about returning an accessor object that provides a thread-safe interface to the Resource
class and/or keeps some lock?
class ResourceGuard {
private:
Resource *resource;
public:
void thread_safe_method() {
resource->lock_and_do_stuff();
}
}
This will be cleared in a RAII fashion, releasing any locks if needed. If you need locking it should be done in the the Resource
class.
Of course you have to take care of the lifespan of Resource
. A very simple way would be to use a std::shard_ptr
. A weak_ptr might fit as well.
Upvotes: 4