最近看代码的时候看到了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_ */
#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;
}
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源码中的后面一个方法注释掉。