我经常听到很多关于c++中的函子。谁能给我一个概述,他们是什么,在什么情况下他们会有用?
当前回答
除此之外,我还使用了函数对象使现有的遗留方法适合命令模式;(我唯一感受到面向对象范式真正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);
此模式的完整上下文在下面的链接中
其他回答
除了在回调中使用,c++函子还可以帮助为矩阵类提供Matlab喜欢的访问样式。这里有一个例子。
小之外。你可以使用boost::function从函数和方法中创建函子,如下所示:
class Foo
{
public:
void operator () (int i) { printf("Foo %d", i); }
};
void Bar(int i) { printf("Bar %d", i); }
Foo foo;
boost::function<void (int)> f(foo);//wrap functor
f(1);//prints "Foo 1"
boost::function<void (int)> b(&Bar);//wrap normal function
b(1);//prints "Bar 1"
你可以使用boost::bind为这个函子添加状态
boost::function<void ()> f1 = boost::bind(foo, 2);
f1();//no more argument, function argument stored in f1
//and this print "Foo 2" (:
//and normal function
boost::function<void ()> b1 = boost::bind(&Bar, 2);
b1();// print "Bar 2"
最有用的是,使用boost::bind和boost::function,你可以从类方法创建函子函数,实际上这是一个委托:
class SomeClass
{
std::string state_;
public:
SomeClass(const char* s) : state_(s) {}
void method( std::string param )
{
std::cout << state_ << param << std::endl;
}
};
SomeClass *inst = new SomeClass("Hi, i am ");
boost::function< void (std::string) > callback;
callback = boost::bind(&SomeClass::method, inst, _1);//create delegate
//_1 is a placeholder it holds plase for parameter
callback("useless");//prints "Hi, i am useless"
你可以创建函子的列表或向量
std::list< boost::function<void (EventArg e)> > events;
//add some events
....
//call them
std::for_each(
events.begin(), events.end(),
boost::bind( boost::apply<void>(), _1, e));
所有这些东西都有一个问题,编译器错误消息不是人类可读的:)
如上所述,函子是可以被视为函数的类(重载操作符())。
在需要将某些数据与对函数的重复或延迟调用相关联的情况下,它们非常有用。
例如,函子链表可用于实现基本的低开销同步协程系统、任务分派器或可中断文件解析。 例子:
/* prints "this is a very simple and poorly used task queue" */
class Functor
{
public:
std::string output;
Functor(const std::string& out): output(out){}
operator()() const
{
std::cout << output << " ";
}
};
int main(int argc, char **argv)
{
std::list<Functor> taskQueue;
taskQueue.push_back(Functor("this"));
taskQueue.push_back(Functor("is a"));
taskQueue.push_back(Functor("very simple"));
taskQueue.push_back(Functor("and poorly used"));
taskQueue.push_back(Functor("task queue"));
for(std::list<Functor>::iterator it = taskQueue.begin();
it != taskQueue.end(); ++it)
{
*it();
}
return 0;
}
/* prints the value stored in "i", then asks you if you want to increment it */
int i;
bool should_increment;
int doSomeWork()
{
std::cout << "i = " << i << std::endl;
std::cout << "increment? (enter the number 1 to increment, 0 otherwise" << std::endl;
std::cin >> should_increment;
return 2;
}
void doSensitiveWork()
{
++i;
should_increment = false;
}
class BaseCoroutine
{
public:
BaseCoroutine(int stat): status(stat), waiting(false){}
void operator()(){ status = perform(); }
int getStatus() const { return status; }
protected:
int status;
bool waiting;
virtual int perform() = 0;
bool await_status(BaseCoroutine& other, int stat, int change)
{
if(!waiting)
{
waiting = true;
}
if(other.getStatus() == stat)
{
status = change;
waiting = false;
}
return !waiting;
}
}
class MyCoroutine1: public BaseCoroutine
{
public:
MyCoroutine1(BaseCoroutine& other): BaseCoroutine(1), partner(other){}
protected:
BaseCoroutine& partner;
virtual int perform()
{
if(getStatus() == 1)
return doSomeWork();
if(getStatus() == 2)
{
if(await_status(partner, 1))
return 1;
else if(i == 100)
return 0;
else
return 2;
}
}
};
class MyCoroutine2: public BaseCoroutine
{
public:
MyCoroutine2(bool& work_signal): BaseCoroutine(1), ready(work_signal) {}
protected:
bool& work_signal;
virtual int perform()
{
if(i == 100)
return 0;
if(work_signal)
{
doSensitiveWork();
return 2;
}
return 1;
}
};
int main()
{
std::list<BaseCoroutine* > coroutineList;
MyCoroutine2 *incrementer = new MyCoroutine2(should_increment);
MyCoroutine1 *printer = new MyCoroutine1(incrementer);
while(coroutineList.size())
{
for(std::list<BaseCoroutine *>::iterator it = coroutineList.begin();
it != coroutineList.end(); ++it)
{
*it();
if(*it.getStatus() == 0)
{
coroutineList.erase(it);
}
}
}
delete printer;
delete incrementer;
return 0;
}
当然,这些例子本身并没有多大用处。它们只是展示了函子是如何有用的,函子本身是非常基础和不灵活的,这使得它们不如boost所提供的有用。
函子是一种高阶函数,它将函数应用于参数化(即模板化)类型。它是映射高阶函数的推广。例如,我们可以像这样为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));
这是一个我被迫使用Functor来解决我的问题的实际情况:
我有一组函数(比如20个),它们都是相同的,除了每个函数在3个特定位置调用不同的特定函数。
这是难以置信的浪费和代码复制。通常我会传入一个函数指针,然后在3个位置调用它。(所以代码只需要出现一次,而不是20次。)
但后来我意识到,在每种情况下,特定的功能需要完全不同的参数配置文件!有时2个参数,有时5个参数,等等。
另一种解决方案是有一个基类,其中特定的函数是派生类中的重写方法。但是我真的想要构建所有这些INHERITANCE,只是为了传递一个函数指针????
解决方案:所以我所做的是,我做了一个包装类(一个“Functor”),它能够调用任何我需要调用的函数。我提前设置了它(用它的参数等),然后我传递它而不是函数指针。现在调用的代码可以触发Functor,而不知道内部发生了什么。它甚至可以多次调用它(我需要它调用3次)。
这就是一个实际的例子,在这个例子中,Functor被证明是一个明显而简单的解决方案,它允许我将代码重复从20个函数减少到1个。