STL排序使用交换还是二进制拷贝?

Does STL sort use swap or binary copy?

本文关键字:二进制 拷贝 交换 排序 STL      更新时间:2023-10-16

我很难找到一个好的答案。出于某种原因,我认为STL排序应该使用swap来实现,以便更好地支持复杂类型,但是当我最终深入研究代码时,它似乎实际上是在进行二进制复制。有人能证实吗?我想二进制拷贝实际上比交换更可取。

附带问题:是否有STL算法或容器操作使用swap实现?(显然在std::swap之外。)我想知道什么时候为复杂类型实现我自己的交换是谨慎的。

编辑:我问的原因是如果你有这样的东西:

class MyClass {
  vector<int> vec_data;
  int a;
  int b;
}
vector<MyClass> my_vec;
sort(my_vec.begin(), my_vec.end(), MyCustomCompare);

我想确保排序没有调用vector的复制构造函数,如果调用MyData的默认复制构造函数,就会发生这种情况。因此,我的问题是排序调用交换,复制分配等?

不,c++标准库中的std::sort不允许对具有非平凡复制/赋值操作符的对象进行二进制复制。我不明白为什么它不能用简单的复制/赋值操作符对对象进行二进制复制。考虑这个对象:

class Explosive {
    Explosive* const self;
public:
    Explosive() :self(this) {}
    Explosive(const Explosive&) :self(this) {}
    ~Explosive() {assert(this==self);}
    Explosive& operator=(const Explosive& rhs) {
        assert(this==self && rhs.self==&rhs); 
        return *this;
    }
    bool operator<(const Explosive& rhs) const 
    {return std::less<Explosive*>(self,rhs.self);}
};

c++算法保证不触发任何断言,这意味着二进制拷贝将无效。

这取决于您的STL实现。GCC STL实现使用了内向排序和插入排序的组合。看来std::swap(或您的专门化)将在内流排序循环中调用,但它不会被插入排序调用。

如果您没有std::swap的专门化,那么默认的std::swap实现将使用临时副本来实现交换。

它不使用二进制拷贝(除了一些POD类型,它们可能有隐藏在STL库深处的专门化)。

而且,在c++ 0x中,插入排序似乎可能会使用移动语义(右值引用)。

我以前看到过std::copy在GNU libstdc++中使用memmove,这取决于元素类型是否为POD has_trivial_assignment_operator。请看这里的源代码:

template<typename _Tp>
   inline _Tp*
   __copy_trivial(const _Tp* __first, const _Tp* __last, _Tp* __result)
   {
      std::memmove(__result, __first, sizeof(_Tp) * (__last - __first));
      return __result + (__last - __first);
   }

至少在SGI中,rotate, reverse, swap_ranges, random_shuffle, partition, next_permutation都使用了swap

见http://www.sgi.com/tech/stl/stl_algo.h

此外,std::sort的c++11标准文档在§25.4.1.1中特别提到:

要求: RandomAccessIterator应满足ValueSwappable(17.6.3.2)的要求。*first的类型应满足MoveConstructible(表20)和MoveAssignable的要求(表22)。

现在§17.6.3.2包含这个:

对象t可与对象u交换当且仅当:

  • 表达式swap(t, u)swap(u, t)在下面描述的上下文中计算时是有效的,并且
  • 这些表达式有以下效果:
    • t引用的对象具有最初由u和
    • 持有的值
    • u所引用的对象具有最初由t持有的值。

计算swap(t, u)swap(u, t)的上下文应确保在候选集合上通过重载解析(13.3)选择名为" swap "的二进制非成员函数,该候选集合包括:

  • (20.2)和
  • 中定义的两个交换函数模板
  • 由参数相关查找(3.4.2)产生的查找集。

它会进行交换,但由于它是一个模板函数,因此它可能会内联交换代码。如果是简单类型,编译器可以选择进行二进制交换作为优化。