您的位置:首页 > 编程语言 > C语言/C++

使用C++标准库sort自定义比较函数导致死循环问题

2016-08-17 09:06 946 查看
永远让比较函数对相等的值返回false(来自Effective
C++)

---------------------------------------------------------------------------------------------------------

转自http://www.cnblogs.com/yuanzz/p/3735213.html

最近写代码,无意中发现了一个坑,关于自定义比较函数的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 }




结果:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息