This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of C++14 status.

### 2104. `unique_lock` move-assignment should not be `noexcept`

Section: 33.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.

Suggested change: remove the `noexcept` specification from `unique_lock::operator=(unique_lock&&)` in 33.6.5.4 [thread.lock.unique] and 33.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` calls `pm->unlock()`.

Now according to the `BasicLockable` requirements:

> `m.unlock()`

> 3 Requires: The current execution agent shall hold a lock on `m`.

> 4 Effects: Releases a lock on `m` 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:

> `unique_lock(mutex_type& m, adopt_lock_t);`

> 11 Requires: The calling thread own the mutex.
> […]
> 13 Postconditions: `pm == &m` and `owns == 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.

The correct fix seems to me to also add a "Throws: Nothing" element to the move-assignment operator, because using it correctly shall not throw an exception.

[Issaquah 2014-02-11: Move to Immediate after SG1 review]

Proposed resolution:

This wording is relative to the FDIS.

1. Change 33.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;
   >         […]
   >       };
   >     }

2. Change 33.6.5.4.2 [thread.lock.unique.cons] around p22 as indicated:

   >     unique_lock& operator=(unique_lock&& u) noexcept;

   > > -22- Effects: If `owns` calls `pm->unlock()`.

   > > -23- Postconditions: `pm == u_p.pm` and `owns == u_p.owns` (where `u_p` is the state of `u` just prior to this construction), `u.pm == 0` and `u.owns == false`.

   > > -24- [Note: With a recursive mutex it is possible for both `*this` and `u` to own the same mutex before the assignment. In this case, `*this` will own the mutex after the assignment and `u` will not. — end note] -??- Throws: Nothing.