std :: sort并不总是调用std :: swap

时间:2013-01-08 10:16:04

标签: c++ stl

请考虑以下代码:

#include <algorithm>
#include <iostream>
#include <vector>

namespace my_space
{

struct A
{
    double  a;
    double* b;
    bool operator<(const A& rhs) const
    {
        return this->a < rhs.a;
    }
};

void swap(A& lhs, A& rhs)
{
    std::cerr << "My swap.\n";
    std::swap(lhs.a, rhs.a);
    std::swap(lhs.b, rhs.b);
}

}

int main()
{
    const int n = 20;
    std::vector<my_space::A> vec(n);
    for (int i = 0; i < n; ++i) {
        vec[i].a = -i;
    }

    for (int i = 0; i < n; ++i) {
        std::cerr << vec[i].a << " ";
    }
    std::cerr << "\n";
    std::sort(vec.begin(), vec.end());
    for (int i = 0; i < n; ++i) {
        std::cerr << vec[i].a << " ";
    }
    std::cerr << "\n";
}

如果我使用n=20,则调用自定义交换函数并对数组进行排序。但是,如果我使用n=4,则数组已正确排序,但自定义交换函数被调用。这是为什么?如果复制我的对象真的很贵怎么办?

对于此测试,我使用的是gcc 4.5.3。

3 个答案:

答案 0 :(得分:20)

对于小范围,GCC的stdlibc ++(以及其他标准库实现)中的std::sort实现因性能原因而重新出现在插入排序中(它比小范围上的quicksort / introsort更快)。

GCC的插入排序实现反过来不通过std::swap进行交换 - 相反,它一次移动整个值范围,而不是单独交换,从而可能节省性能。相关部分在这里(bits/stl_algo.h:2187,GCC 4.7.2):

typename iterator_traits<_RandomAccessIterator>::value_type
  __val = _GLIBCXX_MOVE(*__i);
_GLIBCXX_MOVE_BACKWARD3(__first, __i, __i + 1);
*__first = _GLIBCXX_MOVE(__val);

_GLIBCXX_MOVE与来自C ++ 11的std::move相同,_GLIBCXX_MOVE_BACKWARD3std::move_backward - 但是,只有定义__GXX_EXPERIMENTAL_CXX0X__时才会出现这种情况;如果没有,那么这些操作就是复制而不是移动!

这样做是将当前位置(__i)的值移动到临时存储,然后将所有先前的值从__first移到__i一个,然后重新在__first插入临时值。因此,这会在一次操作中执行 n 交换,而不必将 n 值移动到临时位置:

  first           i
+---+---+---+---+---+---+
| b | c | d | e | a | f |
+---+---+---+---+---+---+
                  |
  <---------------+


  first           i
+---+---+---+---+---+---+
| --> b-> c-> d-> e-> f |
+---+---+---+---+---+---+


  first           i
+---+---+---+---+---+---+
| a | b | c | d | e | f |
+---+---+---+---+---+---+
  ^

答案 1 :(得分:3)

根据类型的不同,交换可能比移动分配更昂贵(在C ++ 98中,一个简单的赋值)。标准库没有任何方法可以检测这些情况。至少在C ++ 11中,解决方案很明确:为实现swap的所有类实现一个move-assignment运算符。

答案 2 :(得分:1)

我将代码修改为更详细。 20个元素的排序使用了许多交换,使用赋值结束副本。对4个元素进行排序仅使用赋值和副本。不知道规格,但它可能会继续下去。

#include <algorithm>
#include <iostream>
#include <vector>

namespace my_space
{

struct A
{
    double  a;
    double* b;
    A()
        : a(0)
        , b(NULL)
    { }
    A(const A &rhs)
        : a(rhs.a)
        , b(rhs.b)
    {
        std::cerr << "copy" << std::endl;
    }
    A& operator=(A const &rhs)
    {
        if(this==&rhs)
                return *this;
        a = rhs.a;
        b = rhs.b;
        std::cerr << "=" << std::endl;
        return *this;
    }
    bool operator<(const A& rhs) const
    {
        return this->a < rhs.a;
    }
};

void swap(A& lhs, A& rhs)
{
    std::cerr << "My swap.\n";
    std::swap(lhs.a, rhs.a);
    std::swap(lhs.b, rhs.b);
}

} // namespace my_space

int main()
{
    const int n = 20;

        std::cerr << "=== TEST CASE: n = " << n << std::endl;
    std::cerr << "=== FILL ===" << std::endl;
    std::vector<my_space::A> vec(n);
    for (int i = 0; i < n; ++i) {
        vec[i].a = -i;
    }

    std::cerr << "=== PRINT ===" << std::endl;
    for (int i = 0; i < n; ++i) {
        std::cerr << vec[i].a << " ";
    }
    std::cerr << "\n";

    std::cerr << "=== SORT ===" << std::endl;
    std::sort(vec.begin(), vec.end());

    std::cerr << "=== PRINT ===" << std::endl;
    for (int i = 0; i < n; ++i) {
        std::cerr << vec[i].a << " ";
    }
    std::cerr << "\n";
}

输出

=== TEST CASE: n = 4
=== FILL ===
copy
copy
copy
copy
=== PRINT ===
0 -1 -2 -3
=== SORT ===
copy
=
=
copy
=
=
=
copy
=
=
=
=
=== PRINT ===
-3 -2 -1 0

=== TEST CASE: n = 20
=== FILL ===
copy
copy
copy
copy
copy
copy
copy
copy
copy
copy
copy
copy
copy
copy
copy
copy
copy
copy
copy
copy
=== PRINT ===
0 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19
=== SORT ===
copy
My swap.
My swap.
My swap.
My swap.
My swap.
My swap.
My swap.
My swap.
My swap.
My swap.
copy
copy
=
copy
copy
=
copy
copy
=
copy
copy
=
copy
copy
=
copy
copy
=
copy
copy
=
copy
copy
=
copy
copy
=
copy
copy
=
copy
copy
=
copy
copy
=
copy
copy
=
copy
copy
=
copy
copy
=
copy
=
copy
=
copy
=
copy
=
=== PRINT ===
-19 -18 -17 -16 -15 -14 -13 -12 -11 -10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0