In general it is a good practice to declare a swap and move noexcept as that allows to provide some exception guarantee. At the same time writing a thread-safe class often implies adding a mutex protecting the internal resources from races. If I want to implement a swap function for such a class the straightforward solution is to lock in a safe way the resources of both arguments of the swap and then perform the resource swap as, for example, clearly answered in the answer to this question: Implementing swap for class with std::mutex .
The problem with such an algorithm is that a mutex lock is not noexcept, therefore swap cannot, strictly speaking, be noexcept. Is there a solution to safely swap two objects of a class with a mutex?
The only possibility that comes to my mind is to store the resource as a handle so that the swap becomes a simple pointer swap which can be done atomically. Otherwise one could consider the lock exceptions as unrecoverable error which should anyway terminate the program, but this solution feels like just a way to put the dust under the carpet.
EDIT: As came out in the comments, I know that the exceptions thrown by the mutexes are not arbitrary but then the question can be rephrased as such:
Are there robust practices to limit the situation a mutex can throw to those when it is actually an unrecoverable OS problem? What comes to my mind is to check, in the swap algorithm, whether the two objects to swap are not the same. That is a clear deadlock situation which will trigger an exception in the best case scenario but can be easily checked for. Are there other similar triggers which one can safely check to make a swap function robust and practically noexcept for all the situation that matter?
On POSIX systems it is common for std::mutex
to be a thin wrapper around pthread_mutex_t
, for which lock and unlock function can fail when:
Both of the above are UB in C++ and are not even guaranteed to be returned by POSIX. On Windows both are UB if std::mutex
is a wrapper around SRWLOCK
.
So it seems that the main point of allowing lock
and unlock
functions to throw is to signal about errors in program, not to make programmer expect and handle them.
This is confirmed by the recommended locking pattern: the destructor ~unique_lock
is noexcept(true)
, but is supposed to call unlock
which is noexcept(false)
. That means if exception is thrown by unlock
function, the whole program gets terminated by std::terminate
.
The standard also mentions this:
The error conditions for error codes, if any, reported by member functions of the mutex types shall be:
(4.1) —
resource_unavailable_try_again
— if any native handle type manipulated is not available.(4.2) —
operation_not_permitted
— if the thread does not have the privilege to perform the operation.(4.3) —
invalid_argument
— if any native handle type manipulated as part of mutex construction is incorrect
In theory you might encounter operation_not_permitted
error, but situations when this happens are not really defined in the standard.
So unless you cause UB in your program related to the std::mutex
usage or use the mutex in some OS-specific scenario, quality implementations of lock
and unlock
should never throw.
Among the common implementations, there is at least one that might be of low quality: std::mutex
implemented on top of CRITICAL_SECTION
in old versions of Windows (I think Windows XP and earlier) can throw after failing to lazily allocate internal event during contention. On the other hand, even earlier versions allocated this event during initialization to prevent failing later, so std::mutex::mutex
constructor might need to throw there (even though it is noexcept(true)
in the standard).