【问题标题】:Does STL sort use swap or binary copy?STL 排序是使用交换还是二进制复制?
【发布时间】:2012-01-03 09:46:25
【问题描述】:

我很难找到一个好的答案。出于某种原因,我认为 STL 排序将使用交换实现以更好地支持复杂类型,但当我最终深入研究代码时,它似乎实际上是在进行二进制复制。有人可以证实这一点吗?我猜二进制副本实际上比交换更受欢迎。

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

编辑:我问的原因是你是否有类似的东西:

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

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

【问题讨论】:

  • 您可能正在查看用于整数(或其他本机)类型的 sort 特化。
  • std::sort 专门用于POD 类型聪明。但是,不允许对所有类型进行二进制复制。
  • 你真的在谈论 STL 吗?
  • @tamulj:如果人们想学究气,“STL”就是原始的 STL。作为标准化过程的一部分,它在 C++ 标准中进行了微小的更改。虽然 C++ 标准并未将标准库的该部分称为 STL,但这通常是 C++ 程序员所说的 STL 的意思。
  • @tamulj,您可以阅读有关 STL 与 C++ 标准库的术语here

标签: c++ sorting stl swap


【解决方案1】:

不,来自 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++ 算法保证不会触发任何一个断言,这意味着二进制副本无效。

【讨论】:

  • 写在哪里(“不允许”)?
  • @sehe:我说“大多数类型都不允许”。允许某些人这样做。如果那不在某个地方的标准中,我会去学习 java。
  • 我不认为原因是它不允许;如果使用二进制副本而不是交换,结果很可能是荒谬的。
  • @Praetorian:我认为演示代码清楚地说明了为什么不允许这样做,尽管我没有明确说明。
  • @sehe: 3.8.5 不允许: ...如果程序有未定义的行为: ...“指针用于访问非静态数据成员或调用非对象的静态成员函数,或"。进行 memcpy 样式复制需要将实现转换为例如先char *,之后就不能再触碰指针的内容了。
【解决方案2】:

这取决于您的 STL 实现。 GCC STL implementation 使用引入排序和插入排序的组合。似乎std::swap(或您的专业化)将在 introsort 循环中被调用,但不会被插入排序调用。

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

它不使用二进制副本(某些 POD 类型除外,它们可能具有隐藏在 STL 库深处的特化)。

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

【讨论】:

  • @Laramie,你不是说:如果你没有专门的交换,你会得到默认的 std::swap。这似乎是我问题的答案。交换的插入排序部分使用复制构造函数和赋值运算符来移动数据。
【解决方案3】:

我以前见过std::copy 在 GNU libstdc++ 中使用 memmove,这取决于元素类型 是否为 POD has_trivial_assignment_operator。见source here

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,rotatereverseswap_rangesrandom_shufflepartitionnext_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) 生成的查找集。

【讨论】:

  • 不是iter_swap?我很惊讶。
  • @BillyONeal iter_swap is redundant,无论如何它是根据交换定义的。我从来没有说过我会详尽无遗!我不可能,因为标准算法有很多实现
【解决方案4】:

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

【讨论】:

  • 技术上,可以选择做这样的优化。
  • @Sehe:编译器也可以。
  • @Mark Ransom - 所以也许我做错了什么,但我在同一个命名空间中为交换和非成员交换编写了一个完全专业的 std 模板(基于 Scott Meyers Effective C++ 的建议book),但是当我 sort() 时似乎都没有被调用,这就是我开始调查的原因。
猜你喜欢
  • 1970-01-01
  • 2019-11-09
  • 2011-04-04
  • 1970-01-01
  • 1970-01-01
  • 2011-06-13
  • 2014-12-24
  • 1970-01-01
  • 2011-03-05
相关资源
最近更新 更多