Std::sort并不总是调用Std::swap
std::sort does not always call std::swap
考虑以下代码:
#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。
对于小范围,GCC的stdlibc++(和其他标准库实现)中的std::sort
实现出于性能原因会重复使用插入排序(在小范围上比快速排序/内联排序更快)。
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_BACKWARD3
是std::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 |
+---+---+---+---+---+---+
^
根据类型的不同,交换操作可能比move-assign(在c++ 98中是一种简单的赋值操作)更昂贵。标准库没有任何方法来检测这些情况。至少在c++ 11中,解决方案是明确的:为所有实现swap的类实现移动赋值操作符。
我将代码修改得更详细。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
- 为什么 std::unique 不调用 std::sort?
- std::cout.imbue()多重调用
- 多个文件的内存分配错误"在抛出 'std :: bad_alloc' what (): std :: bad_alloc 的实例后终止调用" [C++]
- 我收到以下错误:抛出'std::bad_alloc'实例后终止调用
- 从类型std::函数传递变量失败,尽管调用方期望的类型完全相同
- 如果 std::vector::clear() 不是静态的,如何在没有实例的情况下调用它?
- 绑定派生类方法C++从实例范围之外的分隔 std::function 变量调用
- 使用 std::variant<...时调用 BaseState 函数而不是派生函数>
- 类型擦除的std::function与虚拟函数调用的开销
- 如何调用存储在指向"std::函数"的指针中的 lambda?
- 从具有按值捕获的 lambda 移动构造 std::函数时,移动构造函数调用两次
- 将参数打包的参数传递到 std::queue 中,以便稍后使用不同的函数调用
- 如何为 std::vector 分配内存,然后稍后为某些元素调用构造函数?
- std::调用,未找到匹配的重载函数
- 线程 std::调用未知类型,无法专门化函数错误
- 如何使用 std:: 调用函数中的函数?
- 使用经典重载解析规则创建依赖于 std::调用的重载集类
- std::调用 end() 时出现多映射错误
- std::调用函数时找不到函数构造函数
- std::调用没有匹配的重载函数在VS 2015中发现错误