Experience in stl application -- ptr_fun,mem_fun,mem_fun_ref

Keywords: C++ STL

First, let's look at an example.

#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;
}

By compiling and running the above example, we can know that:

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

Both lines of code fail to compile. Compilation error:

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

Why?

According to normal understanding, we passed it to for_ A function address of each algorithm should be able to be compiled normally.

If there is a function f() and an object x, suppose we want to call the function on object X. there are usually three syntax for this call.

  1. If the function f() is not a member function of object x, we call f(x) directly
  2. If function f() is a member function of object x, and X is an object or a reference to an object, then x.f()
  3. If function f() is a member function of object x and p is a pointer to object x, p - > f()

In the above example, we created two containers, vw and vwp, one to store the object Widget and the other to store the pointer to the object Widget.

When the function test() is not a member function of the object Widget, we call the function directly. And the compilation is normal. Ideally, when the function test() is a member function of an object, we should also be able to call it through & Widget:: test.

In fact, the above three calls correspond to the three syntax call methods provided by C + + mentioned earlier. But for_ There is only one each algorithm. Let's take a look at for_ Implementation of 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;
}

Through the above for_ We can clearly see the implementation of each. For_ The implementation of each is based on the syntax we mentioned earlier. This is also a common convention in stl: when a function or function object is called, it always uses the syntax form of non member function.

This proves why our last two calls fail to compile.

So far, maybe mem_fun and mem_fun_ The meaning that REF must exist is obvious, that is, they are used to adjust (the latter two calls) member functions to be called through non member functions.

mem_fun and mem_fun_ref is a function template. There are many ways to implement it. Let's take a look at one of them.

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)())

Takes a pointer argument to a member function__ f. And return a mem_fun_t object.

And mem_fun_t is a subclass of functions. It holds the pointer to the member function and provides the operator() function. In operator(), it calls the member function passed by the parameter.

So we can modify the code in the previous example.

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

To call member functions normally.

Our first call.

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

Pass to for_each is a real function, so we don't have to do for_ Adjust the syntax of each.

We also mentioned PTR earlier_ Fun, let's take a look at PTR first_ Implementation of 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);
}

If you have read the previous article, you may already know, ptr_fun provides the ability to add type definitions to functions.

For example, in the four standard adapters in stl, type definitions must be used.

For example, our previous call

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

But if we use the following call, we need to use ptr_fun to set the type definition.

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

In fact, many times, if you don't know whether to add PTR or not_ Fun, plus there is always nothing wrong, it will not affect the performance of the algorithm. The only bad thing is that it will cause some obstacles when others read your code.

It can also be added anywhere. When the compiler warns you of an error, just go back and add it.

But mem_fun and mem_fun_ref must be added when calling member functions, because when calling, not only the type definition is introduced, but also the syntax call form is converted to adapt to the algorithm.

Posted by cavendano on Sun, 28 Nov 2021 07:55:59 -0800