STL之函数对象

最近看代码的时候看到了STL中的一个函数对象的用法,查阅了一些资料,也做了一些实验。下面整理了这些资料。

 

首先从刚开始的疑问说起。remove_if算法是STL中常用的一个算法,它含接受三个参数:

template <class _ForwardIter, class _Predicate>
_ForwardIter remove_if(_ForwardIter __first, // 开始的迭代器
                       _ForwardIter __last, // 结束的迭代器
                       _Predicate __pred // 条件谓词,它控制了哪些需要被删除) 
{
  __STL_REQUIRES(_ForwardIter, _Mutable_ForwardIterator);
  __STL_UNARY_FUNCTION_CHECK(_Predicate, bool,
               typename iterator_traits<_ForwardIter>::value_type);
  __first = find_if(__first, __last, __pred);
  _ForwardIter __i = __first;
  return __first == __last ? __first 
                           : remove_copy_if(++__i, __last, __first, __pred);
}

细节内容就不说了,大意是如果把满足第三个参数的东西放在后面的iterator中,返回的是那堆要remove元素的开始iterator。我的程序是这样的,一个vector<string>类型的容器中删除某些满足条件的元素,这里是与“Alice”内容一致的元素。首先定义满足条件的函数对象类型,这个对象将会作为remove_if的第三个参数传入。


文件string_match.hpp

#ifndef STRING_MATCH_HPP_
#define STRING_MATCH_HPP_

#include <string>
#include <functional>

using std::string;

typedef unsigned int UINT;

enum findmodes
{
    FM_INVALID = 0,
    FM_IS,
    FM_STARTSWITH,
    FM_ENDSWITH,
    FM_CONTAINS
};

typedef struct tagFindStr
{
    UINT iMode;
    string szMatchStr;
} FindStr;

typedef FindStr* LPFINDSTR;

class FindMatchingString : public std::unary_function<string, bool>
{
public:
    FindMatchingString(const LPFINDSTR lpFS) : m_lpFS(lpFS) {}

    bool operator()(string& strCmp) const
    {
        bool retVal = false;
        string strMatch = m_lpFS->szMatchStr;
        switch(m_lpFS->iMode)
        {
        case FM_IS:
            retVal = (strCmp == strMatch);
            break;
        case FM_STARTSWITH:
            retVal = (strCmp.find(strMatch)==0);
            break;
        case FM_ENDSWITH:
            retVal = (strCmp.find(strMatch)==(strCmp.length()-strMatch.length()));
            break;
        case FM_CONTAINS:
            retVal = (strCmp.find(strMatch) != string::npos);
            break;
        }
        return retVal;
    }
private:
    LPFINDSTR m_lpFS;
};


#endif /* STRING_MATCH_HPP_ */

接着就是使用remove_if方法了。文件main.cpp


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

#include "header/string_match.hpp"

using std::vector;
using std::string;
using std::cout;
using std::endl;
//using std::remove_if;

int main(int argc, char* argv[])
{
    vector<string> vs;
    vs.push_back("Alice");
    vs.push_back("Bob");
    vs.push_back("Cart");
    vs.push_back("Duncan");
    vs.push_back("Kelvin");

    cout << "Before remove:\n[";
    for (vector<string>::iterator iter=vs.begin();iter!=vs.end();iter++)
    {
        cout << *iter << ", ";
    }
    cout << "]" << endl;

    FindStr fs;
    fs.iMode = FM_CONTAINS;
    fs.szMatchStr = "Alice";
    vs.erase(std::remove_if(vs.begin(), vs.end(), FindMatchingString(&fs)), vs.end());

    cout << "After remove:\n[" ;
    for (vector<string>::iterator iter=vs.begin();iter!=vs.end();iter++)
    {
        cout << *iter << ", ";
    }
    cout << "]" << endl;
     return 0;
}

注意main.cpp中的下面一行

vs.erase(std::remove_if(vs.begin(), vs.end(), FindMatchingString(&fs)), vs.end());
FindMatchingString就是一个函数对象(function object)


STL中有很多算法函数(包含在algorithm)都需要提供一个称为函数对象(Function Object)类型的参数。为什么需要这样一个参数?首先这些算法通常是对容器(即存放一堆同类元素的对象,比如list、vector)内的诸多元素进行某种操作,比如sort是对该容器内的这些元素进行排序、find_if是查找该容器内符合某种条件的元素、count_if是计算该容器内符合某种条件的元素总数。为了算法可复用,其中的子操作应该是参数化的,比如sort的排序原则(顺序、逆序)、find_if/count_if的匹配条件。函数对象就是用来描述这些子操作的。上面所提到的函数对象实际上就是remove_if的匹配条件。


这里的FindMatchingString是从unary_function中继承而来,unary_function的定义很简单,只是约定了函数的参数类型和返回值类型,而且仅仅是很简单的typedef,貌似后面也没怎么用到,估计只是给哪些算法的接口:

template <class _Arg, class _Result>
struct unary_function {
  typedef _Arg argument_type;
  typedef _Result result_type;
};

使用unary_function的方法之一从它继承,并且重载_Result operator()(_Arg)const运算符。FindMatchingString这个类就是这样干的。从FindMatchingString的实现过程中可以看出unary_function的两个模板参数其实并没有强制约束,貌似必须靠程序员手工在()运算符重载过程中手工约束。第二种方法是使用std::ptr_fun直接将函数指针转换为一种unary_function对象。不过针对这个上面这个例子就比较诡异了。首先需要定义function而不是class:

bool findEqualString(string& strcmp)
{
	return strcmp == "Alice";
}
诡异的地方是把“Alice”写进了函数,这貌似和题意不符,暂且不管,首要的是看怎么用ptr_fun实现。接着用下面这行代替原来的erase过程
vs.erase(std::remove_if(vs.begin(), vs.end(), std::ptr_fun(findEqualString)), vs.end());
这样就可以运行了。ptr_fun接受一个函数指针,这个函数指针只接受一个函数参数,将这个函数转换为一个pointer_to_unary_function函数对象:
  template<typename _Arg, typename _Result>
    inline pointer_to_unary_function<_Arg, _Result>
    ptr_fun(_Result (*__x)(_Arg))
    { return pointer_to_unary_function<_Arg, _Result>(__x); }
在看看pointer_to_unary_function这个ptr_fun返回对象的定义:
  template<typename _Arg, typename _Result>
    class pointer_to_unary_function : public unary_function<_Arg, _Result>
    {
    protected:
      _Result (*_M_ptr)(_Arg);

    public:
      pointer_to_unary_function() { }

      explicit
      pointer_to_unary_function(_Result (*__x)(_Arg))
      : _M_ptr(__x) { }

      _Result
      operator()(_Arg __x) const
      { return _M_ptr(__x); }
    };
看到了_Result operator()(_Arg __x) const这个运算符重载了,实际上就是调用了ptr_fun参数中的函数。在这个例子中,findEqualString应该接受两个参数,一个参数是vector中的各个String,另外的就是我们这里指定的"Alice",但是unary_function只支持一个参数的函数对象,于是乎binary_function就粉墨登场了。将findEqualString改为两个参数的函数:
bool findEqualString(string& s1, string& s2)
{
	return s1 == s2;
}
仍然用下面这一行代替上面的erase哪行:
string matchStr = "Alice";
vs.erase(std::remove_if(vs.begin(), vs.end(), std::bind2nd(std::ptr_fun(findEqualString), matchStr)));
最后运行的效果和上面的一摸一样。只不过这里的ptr_fun原型是下面的了:
  template<typename _Arg1, typename _Arg2, typename _Result>
    inline pointer_to_binary_function<_Arg1, _Arg2, _Result>
    ptr_fun(_Result (*__x)(_Arg1, _Arg2))
    { return pointer_to_binary_function<_Arg1, _Arg2, _Result>(__x); }
可以看出他是返回一个pointer_to_binary_function的对象,该对象是从binary_function继承而来。而bind2nd方法则是帮顶函数的第二个参数为"Alice"。这里有个小插曲,我的mingw在这里报错了,因为bind2nd返回的binder2nd对象有两个重载()运算符的方法:
      typename _Operation::result_type
      operator()(const typename _Operation::first_argument_type& __x) const
      { return op(__x, value); }

      // _GLIBCXX_RESOLVE_LIB_DEFECTS
      // 109.  Missing binders for non-const sequence elements
      typename _Operation::result_type
      operator()(typename _Operation::first_argument_type& __x) const
      { return op(__x, value); }
在mingw的binder.h的头文件中,报错是说这两个方法不能被overloaded(重载)。我的解决方法是将stl源码中的后面一个方法注释掉。


你可能感兴趣的:(算法,function,String,iterator,Class,fun)