I'm trying to create a very simple, bare-bones C++ class to implement a thread-safe list, i.e. one which is automatically locked when you access it. Unfortunately, the compiler doesn't want to allow me to create and return a struct that contains a unique_lock. This is what I tried at first:
template<typename T>
struct LockedQueue {
private:
std::mutex mutex;
using lock_t = std::unique_lock<std::mutex>;
std::list<T> underlying_list;
public:
struct LockedListAccess {
private:
lock_t lock;
public:
std::list<T> &access;
};
LockedListAccess locked() {
return LockedListAccess{ lock_t{mutex}, underlying_list };
}
};
This fails with
no matching function for call to ‘LockedQueue<tcp::socket>::LockedListAccess::LockedListAccess(<brace-enclosed initializer list>)
I'm guessing this means that brace initializer lists/C++11 unified initialization for structs don't work with move-only types like std::unique_lock. So I tried creating an explicit constructor for my struct that takes the unique_lock as an rvalue reference, and moves it into the member:
template<typename T>
struct LockedQueue {
private:
std::mutex mutex;
using lock_t = std::unique_lock<std::mutex>;
std::list<T> underlying_list;
public:
struct LockedListAccess {
private:
lock_t lock;
public:
std::list<T> &access;
LockedListAccess(lock_t&& l, std::list<T>& a) :
lock(l), access(a) {};
};
LockedListAccess locked() {
return LockedListAccess{ std::move(lock_t{mutex}), underlying_list };
}
};
However, this also fails, giving me the error
error: use of deleted function ‘std::unique_lock<_Mutex>::unique_lock(const std::unique_lock<_Mutex>&) [with _Mutex = std::mutex]’
This compiler error is especially confusing, because it points at the line containing lock(l), access(a) as the one where I'm trying to use the deleted copy constructor of std::unique_lock. I declared l as a lock_t&&, so how could I possibly be calling the copy constructor?
Most resources I could find on the internet seem to indicate that you can move unique_locks around with std::move, though no one seems to address the question of how to construct an object that contains a unique_lock by using std::move. What could I be doing wrong here?
Problem:
In the context of the LockedListAccess constructor, lock_t&& l is actually an l-value. So you need to cast it back to an r-value using std::move. General rule of thumb: Always std::move r-value references and always std::forward forwarding references.
Solution:
LockedListAccess(lock_t&& l, std::list<T>& a)
: lock(std::move(l))
, access(a)
{}
Also you do not need to move the temporary lock object because the compiler will automatically bind a temp object to an r-value reference.
This:
LockedListAccess locked() {
return LockedListAccess{ std::move(lock_t{mutex}), underlying_list }
}
can become this:
LockedListAccess locked() {
return LockedListAccess{ lock_t{mutex}, underlying_list }
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With