stl使用中的经验(十四)--ptr_fun、mem_fun、mem_fun_ref

首先我们看个例子。

#include <iostream>
#include <vector> 
#include <algorithm> 
#include <iterator>
using namespace std;

class Widget{
	public:
	Widget(int a) : m_a(a)
	{
	}
	
	int value() const
	{
		return m_a;
	}
	
	bool test()
	{
		return m_a % 3;
	}
	
private:
	int m_a;
};

bool test(const Widget& w)
{
	return w.value() % 3;
}

int main()
{
	typedef vector<Widget> WdgVec;
	typedef vector<Widget*> WdgPtrVec;
	typedef vector<Widget>::iterator WdgIter;
	typedef vector<Widget*>::iterator WdgPtrIter;
	
	WdgVec vw;
	WdgPtrVec vwp;
	
	for(int index = 0; index < 10; ++index)
	{
		Widget w(index);
		vw.push_back(w);
		
		vwp.push_back(new Widget(index));
	}
	
	for_each(vw.begin(), vw.end(), test);
	for_each(vw.begin(), vw.end(), &Widget::test);
	for_each(vwp.begin(), vwp.end(), &Widget::test);
	return 0;
}

通过编译运行上面的例子,我们能够得知,其中:

for_each(vw.begin(), vw.end(), &Widget::test);
for_each(vwp.begin(), vwp.end(), &Widget::test);

这两行代码都是编译不通过的。编译报错:

[Error] must use '.*' or '->*' to call pointer-to-member function in '__f (...)', e.g. '(... ->* __f) (...)'

为什么呢?

按照正常理解,我们传给了for_each算法一个函数地址,应该是能够正常编译通过的。

如果有一个函数f()和一个对象x,假设我们要在对象x上调用函数,通常是有三种语法来进行这个调用。

  1. 如果函数f()不是对象x的成员函数,那么我们直接调用 f(x)
  2. 如果函数f()是对象x的成员函数,并且x是一个对象或者一个对象的引用,则 x.f()
  3. 如果函数f()是对象x的成员函数,并且p是一个指向对象x的指针,则 p->f()

在上面的例子中,我们创建了两个容器,vwvwp,一个存放对象Widget,一个存放指向对象Widget的指针。

当函数test()不是对象Widget的成员函数时,我们直接调用了函数。并且编译正常。那么在理想情况下,当函数test()是对象的成员函数时,我们应该也是能够通过&Widget::test来调用它。

其实,在上面的三次调用中,分别对应了我们前面提到的C++提供的三种语法的调用方式。但是for_each算法是只有一个的,我们看下for_each的实现。

template <class _InputIter, class _Function>
_Function for_each(_InputIter __first, _InputIter __last, _Function __f) {
  __STL_REQUIRES(_InputIter, _InputIterator);
  for ( ; __first != __last; ++__first)
    __f(*__first);
  return __f;
}

通过上面for_each的实现我们可以很明显的看出来,for_each的实现是基于我们前面所说的语法一来进行对函数调用的。这也是stl中一种普遍的惯例:函数或者函数对象被调用时,总是使用非成员函数的语法形式。

这也就证明了上面我们的后两次调用为什么编译不通过的。

至此,或许mem_funmem_fun_ref必须存在的意义已经很明显了,也就是他们用来调整(后两种调用方式)成员函数,能够被通过非成员函数的方式调用。

mem_funmem_fun_ref是函数模版,实现方式有多种,我们看一下其中一种。

template <class _Ret, class _Tp>
inline mem_fun_t<_Ret,_Tp> mem_fun(_Ret (_Tp::*__f)())
  { return mem_fun_t<_Ret,_Tp>(__f); }
template <class _Ret, class _Tp>
class mem_fun_t : public unary_function<_Tp*,_Ret> {
public:
  explicit mem_fun_t(_Ret (_Tp::*__pf)()) : _M_f(__pf) {}
  _Ret operator()(_Tp* __p) const { return (__p->*_M_f)(); }
private:
  _Ret (_Tp::*_M_f)();
};
mem_fun(_Ret (_Tp::*__f)())

带一个指向某个成员函数的指针参数__f,并且返回一个 mem_fun_t 的对象。

mem_fun_t是一个函数子类,他拥有该成员函数的指针,并提供了operator()函数,在operator()中调用了通过参数传递进来的对象的成员函数。

所以我们可以修改前面例子中的代码。

for_each(vw.begin(), vw.end(), mem_fun_ref(&Widget::test));
for_each(vwp.begin(), vwp.end(), mem_fun(&Widget::test));

来正常的调用成员函数。

我们的第一个调用。

for_each(vw.begin(), vw.end(), test);

传给for_each的是一个真正的函数,所以,我们就不必要对for_each进行语法调整。

前面我们也提到了ptr_fun,下面我们先看下ptr_fun的实现。

template <class _Arg, class _Result>
inline pointer_to_unary_function<_Arg, _Result> ptr_fun(_Result (*__x)(_Arg))
{
  return pointer_to_unary_function<_Arg, _Result>(__x);
}

如果你已经度过前面的文章的话,你或许应该已经知道了,ptr_fun提供了给函数加上类型定义的功能。

比如在stl中的四个标准的配接器中,必须要使用类型定义。

比如我们前面的调用

for_each(vw.begin(), vw.end(), test);

但是如果当我们使用下面的调用的时候,就需要使用ptr_fun来进行类型定义的设置。

for_each(vw.begin(), vw.end(), not1(test));

其实很多时候如果你并不知道该不该加ptr_fun的时候,加上总是没有错的,也不会影响算法的性能,唯一的不好只是在别人阅读你的代码的时候会造成一些障碍

也可以在任何地方都不加,当编译器报错提醒你的时候再返回来加进去就行

但是mem_funmem_fun_ref是当调用成员函数的时候必须要加的,因为在调用的时候,并不是只引入了类型定义,而且还进行了语法调用形式的转换来适应算法。

上一篇:jerry99的数列--阶乘


下一篇:Linux常用API