for_each,count,mismatch等STL算法在VS2013下的实现以及辅助函数的源码解析

for_each

简单的替代for循环,用于遍历可遍历的容器并执行一定的操作,函数原型为:

template <class InputIterator, class Function>
   Function for_each (InputIterator first, InputIterator last, Function f);

输入有3个,两个向前迭代器first和last,指定一片[first, last)的区间,对于其中的每个元素,则调用函数对象f进行处理。
以下是VS2013中对于这个函数的实现:

template inline
    _Fn1 for_each(_InIt _First, _InIt _Last, _Fn1 _Func)
    {   // perform function for each element
    _DEBUG_RANGE(_First, _Last);
    _DEBUG_POINTER(_Func);
    _For_each(_Unchecked(_First), _Unchecked(_Last), _Func);

    return (_STD move(_Func));
    }

也许是钟爱MFC的缘故,微软的实现版本中很喜欢添加各种自定义的宏定义,并反复地进行封装。此处调用的_DEBUG_RANGE和_DEBUG_POINTER实际是两个函数,分别用以检测[_First, last)区间和_Func这个函数对象是否合法。_Unchecked的功能则是将迭代器对象解引用,转换成指针或是其约定好的其他类型。这些函数的具体逻辑我们将会在稍后讨论。

主逻辑_For_each函数如同预想的一样是简单的遍历整个区域。

template<class _InIt,
    class _Fn1> inline
    void _For_each(_InIt _First, _InIt _Last, _Fn1& _Func)
    {   // perform function for each element
    for (; _First != _Last; ++_First)
        _Func(*_First);
    }

_Func的返回值被忽略,因此这个返回值并没有设么意义,但函数使用C++11的move方法返回一个_Func对象。也就是说这样的写法是被支持的

std::for_each(a, a + 10, show)(23); //a是个int型数组,show是显示输入的数组

count和count_if

都是用于统计容器中符合要求的元素的个数的,所不同的是count是用一个元素进行类比,count_if则是用一个单参数的比较函数。Count和count_if函数的原型如下,输入的[First, last)也只要求向前迭代器即可:

template <class InputIterator, class T>
  typename iterator_traits::difference_type
    count ( ForwardIterator first, ForwardIterator last, const T& value );

template <class InputIterator, class Predicate>
  typename iterator_traits::difference_type
count_if ( ForwardIterator first, ForwardIterator last, Predicate pred );

count方法的实现除了使用_DEBUG_RANGE检查[first, last)是否合法外,其具体实现count_np也是采用简单遍历的方法进行:

template<class _InIt,
    class _Ty> inline
    typename iterator_traits<_InIt>::difference_type
        _Count_np(_InIt _First, _InIt _Last, const _Ty& _Val)
    {   // count elements that match _Val
    typename iterator_traits<_InIt>::difference_type _Count = 0;

    for (; _First != _Last; ++_First)
        if (*_First == _Val)
            ++_Count;
    return (_Count);
    }

这里返回的是迭代器的difference_type,对于一般情况来说,都是整型。
count_if的实现_count_if也是大同小异。

template<class _InIt,
    class _Pr> inline
    typename iterator_traits<_InIt>::difference_type
        _Count_if(_InIt _First, _InIt _Last, _Pr _Pred)
    {   // count elements satisfying _Pred
    typename iterator_traits<_InIt>::difference_type _Count = 0;

    for (; _First != _Last; ++_First)
        if (_Pred(*_First))
            ++_Count;
    return (_Count);
    }

返回值也是迭代器的difference_type。

Mismatch和equal

给定一个范围[first1, last1)和一个迭代器first2,对[first1, last1)和[first2, …)中的元素依次比较,并返回第一组不匹配的对。其函数原型如下:

template <class InputIterator1, class InputIterator2>
  pair
    mismatch (InputIterator1 first1, InputIterator1 last1,
              InputIterator2 first2 );

template <class InputIterator1, class InputIterator2, class BinaryPredicate>
  pair
    mismatch (InputIterator1 first1, InputIterator1 last1,
              InputIterator2 first2, BinaryPredicate pred );

如果[frist1, last1)范围内的值和first2完全相等,则程序会返回last1和first2相应位置。这个匹配函数pred的函数对象也可以由用户直接指定。

Equal函数的调用方法和mismatch的完全一致,只不过其返回两个范围是否完全相等的一个布尔值:

template <class InputIterator1, class InputIterator2>
  bool equal ( InputIterator1 first1, InputIterator1 last1,
               InputIterator2 first2 );

template <class InputIterator1, class InputIterator2, class BinaryPredicate>
  bool equal ( InputIterator1 first1, InputIterator1 last1,
               InputIterator2 first2, BinaryPredicate pred );

从实现的角度来看,不带pred参数的mismatch实现实际上是用equal_to<>函数作为默认函数,然后调用了带pred参数的mismatch的方法。其实如果用默认值方式来实现的话,代码可以更加简洁,

template <class InputIterator1, class InputIterator2, class BinaryPredicate>
  pair
    mismatch (InputIterator1 first1, InputIterator1 last1,
              InputIterator2 first2, BinaryPredicate pred = equal_to<>());

之所以会有这种想法,是因为mismatch外包了三个实现,其调用关系是这样的:
for_each,count,mismatch等STL算法在VS2013下的实现以及辅助函数的源码解析_第1张图片

所有的实现都到达这个函数之后,系统会调用_Is_checked函数来检查_First2是否正确,如果_First2在编译阶段就指向空指针之类的错误,程序会及时报错。因此虽然调用图中返回false_type也会调用最终实现_Mismatch,但实际上是不会调用的,false_type的实现事实上是多余的。
最终的_Mismatch实现是简单的线性探查

template inline
    pair<_InIt1, _InIt2>
        _Mismatch(_InIt1 _First1, _InIt1 _Last1,
            _InIt2 _First2, _Pr _Pred)
    {   // return [_First1, _Last1)/[_First2, ...) mismatch using _Pred
    for (; _First1 != _Last1 && _Pred(*_First1, *_First2); )
        ++_First1, ++_First2;
    return (pair<_InIt1, _InIt2>(_First1, _First2));
    }

equal的实现的调用关系也很复杂,并且一样有奇怪的true_type和false_type调用,以下是equal不带pred函数的调用关系图。
for_each,count,mismatch等STL算法在VS2013下的实现以及辅助函数的源码解析_第2张图片

这样做的目的很简单,unsigned char、signed char和char之间的比相等一直是困扰C++程序员的一个问题,分开各自实现是最稳妥的方法,然而这样做给维护代码带来了很大的压力。

辅助函数_DEBUG_RANGE,_DEBUG_POINTER及其调用层次的猜想

这两个函数是刚才在for_each中发现的三个辅助函数,分别是用来检查范围[first, last),指针first是否合法。
乍一看这两个函数互相之间调用关系“圆环套圆环”,还时不时出现_DEBUG_POINTER2这种看上去很“丑陋”的命名,但仔细分析整个调用关系。发现这其中是存在调用层次的:
for_each,count,mismatch等STL算法在VS2013下的实现以及辅助函数的源码解析_第3张图片
for_each,count,mismatch等STL算法在VS2013下的实现以及辅助函数的源码解析_第4张图片
结合之前mismatch,equal函数的调用关系图,我们将这里的这两个辅助函数的调用层次关系分为以下四层:
1. 调用接口层
这里写图片描述
主要负责给外部函数调用

  1. 添加行号信息
    这里写图片描述
    由于C++中用于标注行号和文件名等程序本身信息需要由宏定义表达,因此这一部分也不得不使用宏定义来组织

  2. 根据调用者是指针还是迭代器iterator进行分割
    这里写图片描述
    大家都知道STL算法既可以接受实际的指针,也可以接受迭代器。迭代器的实现为了保证通用性,实际上在送入的时候是由类定义的,对解引用进行了重载。因此迭代器在操作的时候就一定要实际指针分开了。

  3. 根据不同的迭代器类型设计最适合的算法
    这里写图片描述
    迭代器类型共有5中(输入、输出、前向、双向、随机访问),两种不同的迭代器可能都能实现find算法,但对于随机访问迭代器,可以通过分治算法达到比双向迭代器更高的效率,不同的性质的迭代器可以有不同的实现。之所以要划分这一层,也就是出于这个目的。

不得不说VS2013的代码STL的源码虽然各种自定义类型看的让人晕晕乎乎的,但破开繁复的代码细节,观察整个调用关系,还是能从中看到STL最初的设计思想和微软在架构设计方面的功力。

你可能感兴趣的:(for_each,count,mismatch等STL算法在VS2013下的实现以及辅助函数的源码解析)