Exception safety regarding swap() operation - what's so wrong with that?
-
20-06-2021 - |
Вопрос
I keep reading that swap()
operation, like this for example:
template<class T>
void swap (T &a, T &b)
{
T temp (a);
a = b;
b = temp;
}
is problematic when we are dealing with Exception-safety.
What's so wrong with it? Furthermore, how can we solve it?
Решение
In the general implementation, assuming that any operation of T
can throw
, you cannot provide the strong exception guarantee which implies leaving the state exactly as it was before the operation on the event of a exception. Even if each operation on T
offers the strong exception guarantee:
template<class T>
void swap (T &a, T &b)
{
T temp (a); // [1]
a = b; // [2]
b = temp; // [3]
}
If [1] throws, the input is left untouched, which is good. If [2] throws, and assuming the strong exception guarantee, the values are still untouched, which is good. But if it is [3] that throws, a
has already been modified, which means that after the exception propagates up the stack, the caller will be left with a state that is neither the original nor the final states.
EDIT: Furthermore, how can we solve it?
There is no general solution. But in most cases you can provide a exception safe swap
operation for your types. Consider a vector<T>
, which internally manages it's state through the use of three pointers (begin
, end
, capacity
). The general swap
above can throw (fail to allocate, the constructors for the internal T
might throw...), but it is trivial to provide a no-throw swap
implementation:
template <typename T>
class vector {
T *b,*e,*c;
public:
void swap( vector<T>& rhs ) {
using std::swap;
swap( b, rhs.b );
swap( e, rhs.e );
swap( c, rhs.c );
}
//...
};
template <typename T>
void swap( vector<T>& lhs, vector<T>& rhs ) {
lhs.swap(rhs);
}
Because copying of pointers cannot throw, the swap
above offers the no-throw guarantee, and if you always implement swap
following the pattern above (using std::swap;
followed by unqualified calls to swap
) it will be picked up by ADL as a better match than std::swap
.
Другие советы
Daniel is right of course.
But you should not forget that when it comes to exception safety, swap is mostly considered part of the solution (the "copy and swap idiom"), not the problem. The remaining problem is that if you adopt this idiom, you have to make sure that the copy constructors that swap might call, do not throw exceptions, for the reasons Daniel explained.
the source of the exception has been explained
but you can avoid the exceptions by casting the arguments to unsigned byte*
and swapping byte by byte up to sizeof(T)
note this will not call any constructors or destructors on the objects which may violate some preconditions when there is some self reference going on (when a
points to b
after the swap a
will point to a
)
this is how the swap in the D standard library does this (after it checks the self reference)