35

浏览一些 C++ 问题,我经常看到这样的评论,即对 STL 友好的类应该实现一个swap函数(通常作为朋友)。有人能解释一下这带来了什么好处,STL 如何适应这一点以及为什么这个函数应该作为一个实现friend?

4

7 回答 7

26

对于大多数类,默认交换很好,但是,默认交换并非在所有情况下都是最佳的。最常见的例子是使用指向实现习语的类。与默认交换一样,会复制大量内存,如果您是专门的交换,则可以通过仅交换指针来显着加快速度。

如果可能,它不应该是类的朋友,但是它可能需要访问您的类可能不想在类 API 中公开的私有数据(例如,原始指针)。

于 2010-01-04T11:06:55.050 回答
23

std::swap() 的标准版本适用于大多数可分配的类型。

void std::swap(T& lhs,T& rhs)
{
    T tmp(lhs);
    lhs = rhs;
    rhs = tmp;
} 

但这不是最佳实现,因为它调用了复制构造函数,然后调用了两次赋值运算符。

通过为您的类添加您自己的 std::swap() 版本,您可以实现 swap() 的优化版本。

例如 std::vector。上面定义的默认实现将非常昂贵,因为您需要复制整个数据区域。可能会释放旧数据区域或重新分配数据区域,并为复制的每个项目上的包含类型调用复制构造函数。一个专门的版本有一个非常简单的方法来做 std::swap()

// NOTE this is not real code.
// It is just an example to show how much more effecient swaping a vector could
// be. And how using a temporary for the vector object is not required.
std::swap(std::vector<T>& lhs,std::vector<T>& rhs)
{
    std::swap(lhs.data,rhs.data);  // swap a pointer to the data area
    std::swap(lhs.size,rhs.size);  // swap a couple of integers with size info.
    std::swap(lhs.resv,rhs.resv);
}

因此,如果您的班级可以优化 swap() 操作,那么您可能应该这样做。否则将使用默认版本。

就个人而言,我喜欢将 swap() 实现为非抛出成员方法。然后提供一个专门的 std::swap() 版本:

class X
{
    public:
        // As a side Note:
        //    This is also useful for any non trivial class
        //    Allows the implementation of the assignment operator
        //    using the copy swap idiom.
        void swap(X& rhs) throw (); // No throw exception guarantee
};


  // Should be in the same namespace as X.
  // This will allows ADL to find the correct swap when used by objects/functions in
  // other namespaces.
  void swap(X& lhs,X& rhs)
  {
     lhs.swap(rhs);
  } 
于 2010-01-04T11:02:47.803 回答
17

如果你想交换(例如)两个向量而不知道它们的实现,你基本上必须做这样的事情:

typedef std::vector<int> vec;

void myswap(vec &a, vec &b) {
   vec tmp = a;
   a = b;
   b = tmp;
}

a如果并且包含许多元素,这是无效的,b因为所有这些元素都在a,b和之间复制tmp

但是,如果交换函数知道并可以访问向量的内部,则可能会有更有效的实现:

void std::swap(vec &a, vec &b) {
   // assuming the elements of the vector are actually stored in some memory area
   // pointed to by vec::data
   void *tmp = a.data;
   a.data = b.data;
   b.data = tmp;
   // ...
}

在这个实现中,只需要复制几个指针,而不是像第一个版本中的所有元素。由于这个实现需要访问向量的内部,它必须是一个友元函数。

于 2010-01-04T11:09:34.360 回答
11

我将您的问题解释为基本上三个不同(相关)的问题。

  1. 为什么 STL 需要交换?
  2. 为什么要实施专门的交换(iso 依赖于默认值swap)?
  3. 为什么要实现成朋友呢?

为什么 STL 需要交换?

STL 友好类需要的原因swapswap在许多 STL 算法中用作原始操作。(例如reverse,sortpartition通常使用 来实现swap

为什么要实施专门的交换(iso 依赖于默认值swap)?

您的这部分问题已经有很多(好的)答案。基本上,了解类的内部结构通常可以让您编写更优化的swap函数。

为什么要实现成朋友呢?

STL 算法总是将 swap 作为自由函数调用。因此,它需要作为非成员函数可用才能有用。
而且,由于只有当您可以使用内部结构的知识来编写更有效的交换时,编写自定义交换才是有益的,这意味着您的自由函数将需要访问您的类的内部,因此是朋友。

基本上,它不必朋友,但如果它不需要成为朋友,通常也没有理由实现自定义交换。

请注意,您应该确保自由函数与您的类位于同一名称空间内,以便 STL 算法可以通过 Koening 查找找到您的自由函数。

于 2010-01-04T12:48:01.643 回答
9

交换功能的另一种用途是帮助异常安全代码:http ://www.gotw.ca/gotw/059.htm

于 2010-01-04T11:20:35.033 回答
2

效率:

如果您有一个包含指向数据的(智能)指针的类,那么交换指针可能比交换实际数据更快 - 3 个指针副本与 3 个深副本。

如果你使用 'using std::swap' + 一个不合格的 swap 调用(或者只是一个合格的 boost::swap 调用),那么 ADL 将选择自定义交换函数,从而允许编写高效的模板代码。


安全:

指针交换(原始指针,std::auto_ptr 和 std::tr1::shared_ptr)不会抛出,因此可用于实现非抛出交换。非抛出交换使编写提供强异常保证的代码(事务代码)变得更容易。

一般模式是:

class MyClass 
{
  //other members etc...

  void method()
  {
    MyClass finalState(*this);//copy the current class
    finalState.f1();//a series of funcion calls that can modify the internal
    finalState.f2();//state of finalState and/or throw.
    finalState.f3();

    //this only gets call if no exception is thrown - so either the entire function 
    //completes, or no change is made to the object's state at all.
    swap(*this,finalState);
  }
};

至于是否应该作为朋友执行;交换通常需要了解实现细节。是使用调用成员函数的非朋友还是使用朋友,这是一个品味问题。


问题:

自定义交换通常比单个分配快 - 但单个分配总是比默认的三个分配交换快。如果你想移动一个对象,不可能以通用的方式知道交换或赋值是否是最好的——C++0x 用移动构造函数解决了这个问题。

于 2010-01-04T14:31:52.843 回答
2

要实现赋值运算符:

class C
{
    C(C const&);
    void swap(C&) throw();
    C& operator=(C x) { this->swap(x); return *this; }
};

这是异常安全的,当您通过值传递时,复制是通过复制构造函数完成的,并且当您传递临时值时(通过复制省略),编译器可以优化复制。

于 2010-07-10T14:43:19.973 回答