unique_lock
move-assignment should not be noexcept
Section: 32.6.5.4 [thread.lock.unique] Status: C++14 Submitter: Anthony Williams Opened: 2011-11-27 Last modified: 2017-07-05
Priority: Not Prioritized
View all issues with C++14 status.
Discussion:
I just noticed that the unique_lock
move-assignment operator is declared noexcept
. This
function may call unlock()
on the wrapped mutex, which may throw.
noexcept
specification from unique_lock::operator=(unique_lock&&)
in 32.6.5.4 [thread.lock.unique] and 32.6.5.4.2 [thread.lock.unique.cons].
Daniel:
I think the situation is actually a bit more complex as it initially looks.
First, the effects of the move-assignment operator are (emphasize mine):
Effects: If
owns
callspm->unlock()
.
Now according to the BasicLockable
requirements:
3 Requires: The current execution agent shall hold a lock on
m.unlock()
m
. 4 Effects: Releases a lock onm
held by the current execution agent. Throws: Nothing.
This shows that unlock itself is a function with narrow contract and for this reasons no unlock function of a mutex or lock itself does have a noexcept specifier according to our mental model.
Now the move-assignment operator attempts to satisfy these requirement of the function and calls it only when it assumes that the conditions are ok, so from the view-point of the caller of the move-assignment operator it looks as if the move-assignment operator would in total a function with a wide contract. The problem with this analysis so far is, that it depends on the assumed correctness of the state "owns". Looking at the construction or state-changing functions, there do exist several ones that depend on caller-code satisfying the requirements and there is one guy, who looks most suspicious:11 Requires: The calling thread own the mutex.
unique_lock(mutex_type& m, adopt_lock_t);
[…]
13 Postconditions:pm == &m
andowns == true
.
because this function does not even call lock()
(which may, but is not
required to throw an exception if the calling thread does already own the mutex).
So we have in fact still a move-assignment operator that might throw an exception,
if the mutex was either constructed or used (call of lock) incorrectly.
[Issaquah 2014-02-11: Move to Immediate after SG1 review]
Proposed resolution:
This wording is relative to the FDIS.
Change 32.6.5.4 [thread.lock.unique], class template unique_lock
synopsis as indicated:
namespace std { template <class Mutex> class unique_lock { public: typedef Mutex mutex_type; […] unique_lock(unique_lock&& u) noexcept; unique_lock& operator=(unique_lock&& u)noexcept; […] }; }
Change 32.6.5.4.2 [thread.lock.unique.cons] around p22 as indicated:
unique_lock& operator=(unique_lock&& u)noexcept;-22- Effects: If
-23- Postconditions:owns
callspm->unlock()
.pm == u_p.pm
andowns == u_p.owns
(whereu_p
is the state ofu
just prior to this construction),u.pm == 0
andu.owns == false
. -24- [Note: With a recursive mutex it is possible for both*this
andu
to own the same mutex before the assignment. In this case,*this
will own the mutex after the assignment andu
will not. — end note] -??- Throws: Nothing.