Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can't move a std::unique_lock into a struct

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?

like image 890
Edward Avatar asked Jun 29 '26 11:06

Edward


1 Answers

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 }
}
like image 189
Mohamad Elghawi Avatar answered Jul 02 '26 01:07

Mohamad Elghawi