asterisc
asterisc

Reputation: 23

Is std::lock_guard<std::unique_lock<std::mutex>> valid or recommended?

I need to assert on unique_lock::owns_lock() in a working method, but I dont' want to pass the unique_lock as an argument but use it as a static variable. But that means I cannot use the unique_lock for its RAII behavior. Would it be fair to have code like:

namespace
{
    std::mutex gMtx;
    std::unique_lock<std::mutex> gLock(gMtx, std::defer_lock);
}

void foo()
{
    assert(gLock.owns_lock());
    // ...
}

void bar()
{
    std::lock_guard<std::unique_lock<std::mutex>> lock(gLock);
    //...
    foo();
    //...
}

Thoughts?

Upvotes: 2

Views: 466

Answers (2)

TBBle
TBBle

Reputation: 1476

This appears to be valid, according to the current C++ standard draft, and the same text applies in C++11 with only one paragraph-numbering change.

The thing passed as the template argument to std::lock_guard must be a BasicLockable.

BasicLockable specifies two things:

  1. lock(), which has no requirements or limitations beyond either acquiring the lock or throwing an exception.
  2. unlock(), which can only be called if the lock is held, cannot fail, and cannot throw an exception.

unique_lock::lock appears to satisfy the first clause, but see below the separator line.

unique_lock::unlock satisfies the requirements of the second clause, on the basis that the only circumstance under which unique_lock::unlock may throw is if the lock is not currently held, and BasicLockable requires that the lock is held when unlock is called.

This matches the only response I received when I posited this reading to the isocpp.org std-discussion mailing list.


If the unique_lock is already held, it appears that the requirement for BasicLockable is violated, as the current execution agent holds the lock, and an exception is thrown. Since the exact wording is

If an exception is thrown then a lock shall not have been acquired for the current execution agent.

one could argue that the lock wasn't acquired, as it was already held.

Upvotes: 0

VLL
VLL

Reputation: 10155

The standard is unclear about the validity of this construct.

The template parameter of std::lock_guard must meet BasicLockable requirements, i.e. it must have functions lock() and unlock().

std::unique_lock has these functions, which means the code compiles. However, they do not work as BasicLockable requires. unlock() should throw no exceptions, but std::unique_lock::unlock() can throw exceptions. According to this, using std::lock_guard<std::unique_lock<std::mutex>> should be undefined behavior.

However, as Holt points out in comments, the standard also says that unique_lock meets the BasicLockable requirements. So it is unclear whether the behavior is defined. If you can guarantee that unlock() does not throw (the mutex is not unlocked anywhere before lock_guard is destroyed), it will probably work in practice.

Upvotes: 1

Related Questions