我经常听到很多关于c++中的函子。谁能给我一个概述,他们是什么,在什么情况下他们会有用?
当前回答
函子基本上就是一个定义操作符()的类。这让你可以创建“看起来像”函数的对象:
// this is a functor
struct add_x {
add_x(int val) : x(val) {} // Constructor
int operator()(int y) const { return x + y; }
private:
int x;
};
// Now you can use it like this:
add_x add42(42); // create an instance of the functor class
int i = add42(8); // and "call" it
assert(i == 50); // and it added 42 to its argument
std::vector<int> in; // assume this contains a bunch of values)
std::vector<int> out(in.size());
// Pass a functor to std::transform, which calls the functor on every element
// in the input sequence, and stores the result to the output sequence
std::transform(in.begin(), in.end(), out.begin(), add_x(1));
assert(out[i] == in[i] + 1); // for all i
函子有几个优点。其一,与常规函数不同,它们可以包含状态。上面的例子创建了一个函数,无论你给它什么,它都会加上42。但是值42并不是硬编码的,它是在创建函数实例时作为构造函数参数指定的。我可以创建另一个加法器,只需要用不同的值调用构造函数,就可以加27。这使得它们可以很好地定制。
As the last lines show, you often pass functors as arguments to other functions such as std::transform or the other standard library algorithms. You could do the same with a regular function pointer except, as I said above, functors can be "customized" because they contain state, making them more flexible (If I wanted to use a function pointer, I'd have to write a function which added exactly 1 to its argument. The functor is general, and adds whatever you initialized it with), and they are also potentially more efficient. In the above example, the compiler knows exactly which function std::transform should call. It should call add_x::operator(). That means it can inline that function call. And that makes it just as efficient as if I had manually called the function on each value of the vector.
如果我传递的是一个函数指针,编译器不能立即看到它指向哪个函数,所以除非它执行一些相当复杂的全局优化,否则它必须在运行时解除对指针的引用,然后进行调用。
其他回答
函子也可以用来模拟在函数中定义局部函数。参考这个问题和另一个问题。
但是局部函子不能访问外部的自动变量。lambda (c++ 11)函数是一个更好的解决方案。
函子是一个类似于函数的对象。 基本上,一个定义operator()的类。
class MyFunctor
{
public:
int operator()(int x) { return x * 2;}
}
MyFunctor doubler;
int x = doubler(5);
真正的优点是函子可以保存状态。
class Matcher
{
int target;
public:
Matcher(int m) : target(m) {}
bool operator()(int x) { return x == target;}
}
Matcher Is5(5);
if (Is5(n)) // same as if (n == 5)
{ ....}
函子基本上就是一个定义操作符()的类。这让你可以创建“看起来像”函数的对象:
// this is a functor
struct add_x {
add_x(int val) : x(val) {} // Constructor
int operator()(int y) const { return x + y; }
private:
int x;
};
// Now you can use it like this:
add_x add42(42); // create an instance of the functor class
int i = add42(8); // and "call" it
assert(i == 50); // and it added 42 to its argument
std::vector<int> in; // assume this contains a bunch of values)
std::vector<int> out(in.size());
// Pass a functor to std::transform, which calls the functor on every element
// in the input sequence, and stores the result to the output sequence
std::transform(in.begin(), in.end(), out.begin(), add_x(1));
assert(out[i] == in[i] + 1); // for all i
函子有几个优点。其一,与常规函数不同,它们可以包含状态。上面的例子创建了一个函数,无论你给它什么,它都会加上42。但是值42并不是硬编码的,它是在创建函数实例时作为构造函数参数指定的。我可以创建另一个加法器,只需要用不同的值调用构造函数,就可以加27。这使得它们可以很好地定制。
As the last lines show, you often pass functors as arguments to other functions such as std::transform or the other standard library algorithms. You could do the same with a regular function pointer except, as I said above, functors can be "customized" because they contain state, making them more flexible (If I wanted to use a function pointer, I'd have to write a function which added exactly 1 to its argument. The functor is general, and adds whatever you initialized it with), and they are also potentially more efficient. In the above example, the compiler knows exactly which function std::transform should call. It should call add_x::operator(). That means it can inline that function call. And that makes it just as efficient as if I had manually called the function on each value of the vector.
如果我传递的是一个函数指针,编译器不能立即看到它指向哪个函数,所以除非它执行一些相当复杂的全局优化,否则它必须在运行时解除对指针的引用,然后进行调用。
除此之外,我还使用了函数对象使现有的遗留方法适合命令模式;(我唯一感受到面向对象范式真正OCP之美的地方);在这里还添加了相关的函数适配器模式。
假设你的方法有签名:
int CTask::ThreeParameterTask(int par1, int par2, int par3)
我们将了解如何使它适合Command模式——为此,首先,必须编写一个成员函数适配器,以便可以将其作为函数对象调用。
注意-这是丑陋的,也许你可以使用Boost绑定助手等,但如果你不能或不想这样做,这是一种方法。
// a template class for converting a member function of the type int function(int,int,int)
//to be called as a function object
template<typename _Ret,typename _Class,typename _arg1,typename _arg2,typename _arg3>
class mem_fun3_t
{
public:
explicit mem_fun3_t(_Ret (_Class::*_Pm)(_arg1,_arg2,_arg3))
:m_Ptr(_Pm) //okay here we store the member function pointer for later use
{}
//this operator call comes from the bind method
_Ret operator()(_Class *_P, _arg1 arg1, _arg2 arg2, _arg3 arg3) const
{
return ((_P->*m_Ptr)(arg1,arg2,arg3));
}
private:
_Ret (_Class::*m_Ptr)(_arg1,_arg2,_arg3);// method pointer signature
};
同样,我们需要一个辅助方法mem_fun3来帮助调用上面的类。
template<typename _Ret,typename _Class,typename _arg1,typename _arg2,typename _arg3>
mem_fun3_t<_Ret,_Class,_arg1,_arg2,_arg3> mem_fun3 ( _Ret (_Class::*_Pm) (_arg1,_arg2,_arg3) )
{
return (mem_fun3_t<_Ret,_Class,_arg1,_arg2,_arg3>(_Pm));
}
现在,为了绑定参数,我们必须写一个绑定函数。所以,是这样的:
template<typename _Func,typename _Ptr,typename _arg1,typename _arg2,typename _arg3>
class binder3
{
public:
//This is the constructor that does the binding part
binder3(_Func fn,_Ptr ptr,_arg1 i,_arg2 j,_arg3 k)
:m_ptr(ptr),m_fn(fn),m1(i),m2(j),m3(k){}
//and this is the function object
void operator()() const
{
m_fn(m_ptr,m1,m2,m3);//that calls the operator
}
private:
_Ptr m_ptr;
_Func m_fn;
_arg1 m1; _arg2 m2; _arg3 m3;
};
并且,一个使用binder3类的辅助函数- bind3:
//a helper function to call binder3
template <typename _Func, typename _P1,typename _arg1,typename _arg2,typename _arg3>
binder3<_Func, _P1, _arg1, _arg2, _arg3> bind3(_Func func, _P1 p1,_arg1 i,_arg2 j,_arg3 k)
{
return binder3<_Func, _P1, _arg1, _arg2, _arg3> (func, p1,i,j,k);
}
现在,我们必须在Command类中使用它;使用以下类型定义:
typedef binder3<mem_fun3_t<int,T,int,int,int> ,T* ,int,int,int> F3;
//and change the signature of the ctor
//just to illustrate the usage with a method signature taking more than one parameter
explicit Command(T* pObj,F3* p_method,long timeout,const char* key,
long priority = PRIO_NORMAL ):
m_objptr(pObj),m_timeout(timeout),m_key(key),m_value(priority),method1(0),method0(0),
method(0)
{
method3 = p_method;
}
你是这样称呼它的:
F3 f3 = PluginThreadPool::bind3( PluginThreadPool::mem_fun3(
&CTask::ThreeParameterTask), task1,2122,23 );
注意:f3 ();将调用方法task1->ThreeParameterTask(21,22,23);
此模式的完整上下文在下面的链接中
函子是一种高阶函数,它将函数应用于参数化(即模板化)类型。它是映射高阶函数的推广。例如,我们可以像这样为std::vector定义一个函子:
template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::vector<U> fmap(F f, const std::vector<T>& vec)
{
std::vector<U> result;
std::transform(vec.begin(), vec.end(), std::back_inserter(result), f);
return result;
}
这个函数接受一个std::vector<T>,并在给定一个接受T并返回U的函数F时返回std::vector<U>。一个函子不一定要在容器类型上定义,它也可以为任何模板类型定义,包括std::shared_ptr:
template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::shared_ptr<U> fmap(F f, const std::shared_ptr<T>& p)
{
if (p == nullptr) return nullptr;
else return std::shared_ptr<U>(new U(f(*p)));
}
下面是一个将类型转换为double类型的简单示例:
double to_double(int x)
{
return x;
}
std::shared_ptr<int> i(new int(3));
std::shared_ptr<double> d = fmap(to_double, i);
std::vector<int> is = { 1, 2, 3 };
std::vector<double> ds = fmap(to_double, is);
函子应该遵循两条定律。第一个是恒等定律,它指出,如果函子给定了恒等函数,它应该与将恒等函数应用于类型相同,即fmap(identity, x)应该与identity(x)相同:
struct identity_f
{
template<class T>
T operator()(T x) const
{
return x;
}
};
identity_f identity = {};
std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<int> is1 = fmap(identity, is);
std::vector<int> is2 = identity(is);
下一个定律是组合定律,它指出,如果函子被赋予两个函数的组合,它应该与将函子应用于第一个函数,然后再应用于第二个函数相同。因此,fmap(std::bind(f, std::bind(g, _1)), x)应该与fmap(f, fmap(g, x))相同:
double to_double(int x)
{
return x;
}
struct foo
{
double x;
};
foo to_foo(double x)
{
foo r;
r.x = x;
return r;
}
std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<foo> is1 = fmap(std::bind(to_foo, std::bind(to_double, _1)), is);
std::vector<foo> is2 = fmap(to_foo, fmap(to_double, is));