Domanda

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?

È stato utile?

Soluzione

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.

Altri suggerimenti

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.

See: What is the copy-and-swap idiom?

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)

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top