最近写代码,无意中发现了一个坑,关于自定义比较函数的stl sort函数的坑,于是记录下来。
先贴代码:
1 #include <iostream>
2 #include <vector>
3 #include <algorithm>
4
5 struct finder
6 {
7 bool operator()(int first, int second){return first <= second;}
8 } my_finder;
9
10 int main(int argc, char** argv)
11 {
12 int value = atoi(argv[1]);
13 int num = atoi(argv[2]);
14 std::vector<int> vecTest;
15 for(int i=0; i!=num; ++i)
16 vecTest.push_back(value);
17
18 std::sort(vecTest.begin(), vecTest.end(), my_finder);
19 for(int i=0; i!=vecTest.size(); ++i)
20 std::cout<<vecTest[i]<<‘\t‘;
21 std::cout<<std::endl;
22
23 return 0;
24 }
这段代码看上去好好的,实际上却有core的可能。
且看图:
敏思苦想很久,也想不出为啥会core,后来查了资料,才发现了问题所在,现在通过源码分析一下原因。
于是定位到sort函数:
1 template<typename _RandomAccessIterator, typename _Compare>
2 inline void
3 sort(_RandomAccessIterator __first, _RandomAccessIterator __last,
4 _Compare __comp)
5 {
6 typedef typename iterator_traits<_RandomAccessIterator>::value_type
7 _ValueType;
8
9 // concept requirements
10 __glibcxx_function_requires(_Mutable_RandomAccessIteratorConcept<
11 _RandomAccessIterator>)
12 __glibcxx_function_requires(_BinaryPredicateConcept<_Compare, _ValueType,
13 _ValueType>)
14 __glibcxx_requires_valid_range(__first, __last);
15
16 if (__first != __last)
17 {
18 std::__introsort_loop(__first, __last, __lg(__last - __first) * 2,
19 __comp);
20 std::__final_insertion_sort(__first, __last, __comp);
21 }
22 }
这是stl_algo.h中的sort函数,且忽略10-14行的参数检查,实际上sort函数先是用了introsort(内省排序,http://en.wikipedia.org/wiki/Introsort),然后采用了insertsort(插入排序)。
1、我们先来分析内省排序吧。
先来看看__introsort_loop的函数原型
1 template<typename _RandomAccessIterator, typename _Size>
2 void
3 __introsort_loop(_RandomAccessIterator __first,
4 _RandomAccessIterator __last,
5 _Size __depth_limit)
6 {
7 typedef typename iterator_traits<_RandomAccessIterator>::value_type
8 _ValueType;
9
10 while (__last - __first > int(_S_threshold))
11 {
12 if (__depth_limit == 0)
13 {
14 std::partial_sort(__first, __last, __last);
15 return;
16 }
17 --__depth_limit;
18 _RandomAccessIterator __cut =
19 std::__unguarded_partition(__first, __last,
20 _ValueType(std::__median(*__first,
21 *(__first
22 + (__last
23 - __first)
24 / 2),
25 *(__last
26 - 1))));
27 std::__introsort_loop(__cut, __last, __depth_limit);
28 __last = __cut;
29 }
30 }
如果__last - __first > int(_S_threshold)的时候,就开始循环了。
关于_S_threshold的定义:
1 enum { _S_threshold = 16 };
好吧,是写死的,为【16】
注意,我为什么把16标红,这就是坑开始的地方了。如果元素小于16(第10行),就直接略过,开始了:
std::__final_insertion_sort(__first, __last, __comp); //
本次不对其进行分析
我们继续往下走,__depth_limit哪来的呢。看代码:
1 template<typename _Size>
2 inline _Size
3 __lg(_Size __n)
4 {
5 _Size __k;
6 for (__k = 0; __n != 1; __n >>= 1)
7 ++__k;
8 return __k;
9 }
还记得sort调用introsort吗?
std::__introsort_loop(__first, __last,
__lg(__last - __first) * 2, __comp);
当__depth_limit !=
0时,则开始了introsort递归,而真正影响它的是__unguarded_partition函数。
__unguarded_partition函数原型:
1 template<typename _RandomAccessIterator, typename _Tp, typename _Compare>
2 _RandomAccessIterator
3 __unguarded_partition(_RandomAccessIterator __first,
4 _RandomAccessIterator __last,
5 _Tp __pivot, _Compare __comp)
6 {
7 while (true)
8 {
9 while (__comp(*__first, __pivot))
10 ++__first;
11 --__last;
12 while (__comp(__pivot, *__last))
13 --__last;
14 if (!(__first < __last))
15 return __first;
16 std::iter_swap(__first, __last);
17 ++__first;
18 }
19 }
好吧,终于要找到原因了,就是这个__pivot了。
还记得我们自定义的__comp函数吗?
struct finder
{
bool operator()(int first, int second){return
first <= second;}
}
my_finder;
当*__first ==
__pivot的时候,返回了true,然后,执行了16行的元素交换。
那如果像我测试的代码那样,所有元素都相等呢?岂不就是走进了死循环,等着的就可能是越界了。
为什么__unguarded_partition不检查边界呢?有人分析称是为了效率,再大数据排序的时候,每次都需要校验边界,确实也是个很大开销。
后来看人总结:永远让比较函数对相等的值返回false(来自Effective
C++)
附上正确代码:
1 #include <iostream>
2 #include <vector>
3 #include <algorithm>
4
5 struct finder
6 {
7 //永远让比较函数中对相等值返回false
8 bool operator()(int first, int second){return first < second;}
9 } my_finder;
10
11 int main(int argc, char** argv)
12 {
13 int value = atoi(argv[1]);
14 int num = atoi(argv[2]);
15 std::vector<int> vecTest;
16 for(int i=0; i!=num; ++i)
17 vecTest.push_back(value);
18
19 std::sort(vecTest.begin(), vecTest.end(), my_finder);
20 for(int i=0; i!=vecTest.size(); ++i)
21 std::cout<<vecTest[i]<<‘\t‘;
22 std::cout<<std::endl;
23
24 return 0;
25 }
结果:
欢迎转载交流,请注明出处:http://www.cnblogs.com/yuanzz/p/3735213.html