关于 swap() 操作的异常安全 - 这有什么问题?

Exception safety regarding swap() operation - what's so wrong with that?

本文关键字:什么 问题 安全 异常 swap 操作 关于      更新时间:2023-10-16

我一直在读swap()操作,例如:

template<class T>
void swap (T &a, T &b)
{
  T temp (a);
  a = b;
  b = temp;
}

当我们处理异常安全时是有问题的。

它怎么了?此外,我们如何解决它?

在一般实现中,假设T的任何操作都可以throw,则无法提供强异常保证,这意味着在异常事件上保持操作前的状态。即使T上的每个操作都提供了强大的异常保证:

template<class T>
void swap (T &a, T &b)
{
  T temp (a);          // [1]
  a = b;               // [2]
  b = temp;            // [3]
}

如果[1]抛出,则输入保持不变,这很好。如果[2]抛出,并且假设有强大的异常保证,那么这些值仍然没有受到影响,这很好。但是,如果是[3]抛出,则a已经被修改,这意味着在异常向上传播到堆栈之后,调用者将留下一个既不是原始状态也不是最终状态的状态。


编辑:此外,我们如何解决它

没有通用的解决方案。但在大多数情况下,您可以为您的类型提供异常安全的swap操作。考虑一个vector<T>,它通过使用三个指针(beginendcapacity)在内部管理它的状态。上面的一般swap可以抛出(分配失败,内部T的构造函数可能抛出…),但提供一个无抛出的swap实现是微不足道的:

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);
}

因为复制指针不能抛出,所以上面的swap提供了无抛出保证,如果您总是按照上面的模式实现swapusing std::swap;后面是对swap的非限定调用),ADL会认为它比std::swap匹配得更好。

Daniel当然是对的。

但您不应该忘记,当涉及到异常安全时,交换通常被认为是解决方案的一部分("复制和交换习语"),而不是问题。剩下的问题是,如果你采用这个习惯用法,你必须确保swap可能调用的复制构造函数不会抛出异常,这是Daniel解释的原因。

请参阅:什么是复制和交换习语?

已经解释了异常的来源

但是,您可以通过将参数强制转换为unsigned byte*并逐字节交换到sizeof(T)来避免异常。注意,这不会在对象上调用任何构造函数或析构函数,这可能会在发生自引用时违反某些先决条件(当a在交换后指向b时,a将指向a

D标准库中的交换就是这样做的(在检查自引用之后)