2

Why is std::mutex::unlock() not noexept? For some reason the standard leaves the behavior undefined when a thread that does not own the mutex calls unlock() on it. What is the justification for doing so? Doesn't this cause std::unique_lock or std::lock_guard destructors implementations from accidentally leaking exceptions in their destructors if the function throws?

Note Destructors that throw - https://akrzemi1.wordpress.com/2011/09/21/destructors-that-throw/

Curious
  • 20,870
  • 8
  • 61
  • 146
  • 2
    I think it is just a consequence of the comittee's policy to apply noexcept very carefully and only where it is critical for performance and/or for realizing the basic/strong exception guarantee. – MikeMB Jul 19 '16 at 05:43
  • I myself have this question when dealing with exception-safety in code using mutex, specifically what does it *mean* for unlock() to throw and how client code can reasonably deal with it (particularly if used through something like unique_lock, which presumably will cause a termination if an unlock() exception actually happens in its destructor, which is fine as we'd otherwise wind up violating a post-condition). Just seems like, as a very low-level part of the library, this needs to be better specified. Compare it to std::atomic where everything is noexcept (as you'd expect). – Oliver Seiler Apr 30 '20 at 16:58

2 Answers2

2

I can't say why the committee didn't make it noexcept. I can only point out the fact that noexcept is - in general - only applied explicitly to a few key functions throughout the standard library that are necessary for implementing efficient functions providing the strong exception guarantee (like std::swap or move constructors).

As far as std::unique_lock is concerned however: Its destructor IS (implicitly) noexcept, so if an implementation would allow unlock to throw, unique_lock's destructor would have to catch it internally.

MikeMB
  • 20,029
  • 9
  • 57
  • 102
  • 3
    Not really. `BasicLockable` requires `unlock()` to not throw, so if it throws all bets are off - `terminate` is actually a good option there. `unlock()` isn't `noexcept` because it's narrow contract. Trying to unlock a mutex you don't own causes UB, and an implementation might want to throw an exception (e.g., in debug mode) if it can detect it. – T.C. Jul 20 '16 at 21:40
0

std::unique_lock has a flag to indicate if it 'owns' the lock. Once you call lock successfully, it sets the flag. When you call unlock, the flag will be reset.

When the destructor is called, if it doesn't own the lock, it won't call the unlock.

for_stack
  • 21,012
  • 4
  • 35
  • 48
  • Thanks! That's right. But what happens in the case of `lock_guard`? – Curious Jul 19 '16 at 05:48
  • In the constructor of `lock_guard`, it `lock` the `mutex`, i.e. the parameter of the constructor. So `lock_guard` always 'owns' the `mutex`. When it destructs, it MUST `unlock` the `mutex` it owns. – for_stack Jul 19 '16 at 06:04