我一直听到在C++中有关functor的许多内容。能否给我一个概述,告诉我它们是什么以及在什么情况下它们会有用?
我一直听到在C++中有关functor的许多内容。能否给我一个概述,告诉我它们是什么以及在什么情况下它们会有用?
一个函数对象(functor)基本上就是一个定义了operator()
的类。这使得你可以创建像函数一样的对象:
// 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
关于函数对象(functor)有几个好处。其中一个是,与普通函数不同,它们可以包含状态。上面的例子创建了一个函数,它会将给定的值加上42。但是这个值42并不是硬编码的,在创建函数对象实例时,我们通过构造函数参数指定了它。我可以通过使用不同的值调用构造函数来创建另一个加法器,比如加27。这使得它们非常灵活可定制。
正如最后几行所示,你经常将函数对象作为参数传递给其他函数,比如std::transform或其他标准库算法。你也可以使用普通函数指针来做同样的事情,但是如我之前所说,函数对象可以被“定制”,因为它们包含状态,使得它们更加灵活(如果我想使用函数指针,我必须编写一个只能将参数加1的函数。而函数对象是通用的,可以添加任何你初始化的值),而且它们也可能更高效。在上面的例子中,编译器知道应该调用哪个函数std::transform
。它应该调用add_x::operator()
。这意味着它可以内联这个函数调用。这使得它和手动对向量的每个值调用函数一样高效。
add42
,我将使用之前创建的函数对象,并将42添加到每个值。而使用add_x(1)
,我会创建一个新的函数对象实例,它只会将1添加到每个值。这只是为了说明通常情况下,你会在需要时“即兴”创建函数对象,而不是先创建并在实际使用它之前一直保留它。 - jalfoperator()
,因为这是调用者使用的方法。除此之外,函数对象拥有的其他成员函数、构造函数、操作符和成员变量完全由您决定。 - jalfadd42
会被称为函数对象,而不是add_x
(它是函数对象的类或者简称函数对象类)。我认为这种术语使用是一致的,因为函数对象也被称为“函数对象”,而不是函数类。请问您是否需要澄清此点? - Sergei Tachenov小加说明,您可以使用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));
所有这些东西都存在一个问题,编译器错误信息不易读懂 :)
operator()
是否应该是public的,因为类默认为private? - NathanOliverstd::function
和std::bind
。 - phuclv函数对象是一个表现得像函数的对象。基本上,它是定义了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)
{ ....}
在C++出现之前,"functor"这个名称已经在范畴论中传统地使用了很久。这与C++中的"functor"概念无关。最好使用名称函数对象来代替我们在C++中所称呼的"functor",因为其他编程语言也是这样称呼类似的构造体。
与普通函数相比,它的特点是:
缺点:
与函数指针相比,它的特点是:
缺点:
与虚函数相比,它的特点是:
缺点:
foo(arguments)
的对象。因此,它可以包含变量;例如,如果您有一个update_password(string)
函数,您可能想要跟踪它发生的频率;使用函数对象,可以使用一个表示时间戳的private long time
来表示最后一次发生的时间。使用函数指针或普通函数,您需要在其命名空间之外使用一个变量,这仅通过文档和用法直接相关,而不是通过定义相关。 - anon像其他人提到的那样,functor 是一个表现得像函数的对象,即它重载了函数调用运算符。
在STL算法中,常用functor。它们很有用,因为它们可以在函数调用之前和之间保持状态,就像函数式语言中的闭包一样。例如,您可以定义一个 MultiplyBy
functor ,它将其参数乘以指定的数量:
class MultiplyBy {
private:
int factor;
public:
MultiplyBy(int x) : factor(x) {
}
int operator () (int other) const {
return factor * other;
}
};
然后,您可以将MultiplyBy
对象传递给像std::transform这样的算法:
int array[5] = {1, 2, 3, 4, 5};
std::transform(array, array + 5, array, MultiplyBy(3));
// Now, array is {3, 6, 9, 12, 15}
与函数指针相比,函数对象的另一个优点是它可以在更多情况下进行内联调用。如果您将一个函数指针传递给transform
,除非该调用被内联化并且编译器知道您总是将同一个函数传递给它,否则它无法通过指针内联化调用。
transform
等函数。然而,在这种情况下,我们只是在同一调用中构造对象。这是唯一的区别吗?也就是说,一旦transform
完成,函数对象就会超出范围并被销毁吗?谢谢! - rturrado对于像我这样的新手:通过一些研究,我弄清楚了jalf发布的代码是做什么的。
一个函数对象是一个类或结构体对象,可以像函数一样被“调用”。这是通过重载() 运算符
实现的。() 运算符
(不确定它的名称)可以使用任意数量的参数。其他运算符只能取两个值,例如+ 运算符
只能取两个值,并返回您为其重载的任何值。你可以在() 运算符
中放置任意数量的参数,这就赋予了它灵活性。
要创建一个函数对象,首先创建你的类。然后为该类创建一个构造函数,带有您选择的类型和名称的参数。接下来,在同一语句中使用初始化器列表(其中使用单冒号运算符,这也是我新学习的内容),构造具有先前声明的构造函数参数的类成员对象。然后重载() 运算符
。最后声明所创建的类或结构体的私有对象。
我的代码(我发现jalf的变量名令人困惑)
class myFunctor
{
public:
/* myFunctor is the constructor. parameterVar is the parameter passed to
the constructor. : is the initializer list operator. myObject is the
private member object of the myFunctor class. parameterVar is passed
to the () operator which takes it and adds it to myObject in the
overloaded () operator function. */
myFunctor (int parameterVar) : myObject( parameterVar ) {}
/* the "operator" word is a keyword which indicates this function is an
overloaded operator function. The () following this just tells the
compiler that () is the operator being overloaded. Following that is
the parameter for the overloaded operator. This parameter is actually
the argument "parameterVar" passed by the constructor we just wrote.
The last part of this statement is the overloaded operators body
which adds the parameter passed to the member object. */
int operator() (int myArgument) { return myObject + myArgument; }
private:
int myObject; //Our private member object.
};
如果有任何不准确或明显错误的地方,请随意纠正我!
std::vector
定义一个functor: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));
fmap(id, x) = id(x)
和 fmap(f ◦ g, x) = fmap(f, fmap(g, x))
。 - Mateen Ulhaq就像一再强调的那样,函数对象是可以被视为函数的类(重载运算符())。
它们最有用的情况是在需要将一些数据与重复或延迟调用函数相关联的情况下使用。
例如,函数对象的链表可用于实现基本低开销的同步协程系统、任务分发器或可中断的文件解析。
示例:
/* 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;
}
operator()
,但没有利用函数对象的属性。 - renardesque
operator()(...)
是什么意思:它是重载_"函数调用"_运算符。这只是对()
运算符进行操作符重载。不要将operator()
与调用名为operator
的函数混淆,而是将其视为常规的运算符重载语法。 - zardoshtoperator()
? - Gabriel Staples