我一直在听C++中的仿函数.有人可以给我一个关于它们是什么的概述以及在什么情况下它们会有用吗?
仿函数几乎只是一个定义operator()的类.这使您可以创建"看起来像"一个函数的对象:
// this is a functor struct add_x { add_x(int x) : x(x) {} 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::vectorin; // assume this contains a bunch of values) std::vector 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的加法器.这使得它们可以很好地定制.
如最后一行所示,您经常将函子作为参数传递给其他函数,例如std :: transform或其他标准库算法.您可以使用常规函数指针执行相同操作,除了如上所述,仿函数可以"自定义",因为它们包含状态,使它们更灵活(如果我想使用函数指针,我必须编写函数它在参数中添加了1个.函子是通用的,并添加了用它初始化的任何内容,并且它们也可能更有效.在上面的示例中,编译器确切地知道std::transform
应该调用哪个函数.它应该打电话add_x::operator()
.这意味着它可以内联该函数调用.这使得它就像我在向量的每个值上手动调用函数一样高效.
如果我传递了一个函数指针,编译器就无法立即看到它指向哪个函数,所以除非它执行一些相当复杂的全局优化,否则它必须在运行时取消引用指针,然后进行调用.
一点点补充.您可以使用boost::function
,从函数和方法创建仿函数,如下所示:
class Foo { public: void operator () (int i) { printf("Foo %d", i); } }; void Bar(int i) { printf("Bar %d", i); } Foo foo; boost::functionf(foo);//wrap functor f(1);//prints "Foo 1" boost::function b(&Bar);//wrap normal function b(1);//prints "Bar 1"
你可以使用boost :: bind为这个仿函数添加状态
boost::functionf1 = boost::bind(foo, 2); f1();//no more argument, function argument stored in f1 //and this print "Foo 2" (: //and normal function boost::function b1 = boost::bind(&Bar, 2); b1();// print "Bar 2"
最有用的是,使用boost :: bind和boost :: function,你可以从类方法创建functor,实际上这是一个委托:
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> events; //add some events .... //call them std::for_each( events.begin(), events.end(), boost::bind( boost::apply (), _1, e));
所有这些都有一个问题,编译器错误消息不是人类可读的:)
Functor是一个像函数一样运作的对象.基本上,一个定义的类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++概念无关.最好使用名称函数对象而不是我们在C++中称之为"functor"的对象.这就是其他编程语言调用类似结构的方式.
用而不是普通函数:
特征:
函数对象可能具有状态
函数对象适合OOP(它的行为与其他所有对象一样).
缺点:
使程序更加复杂.
使用而不是函数指针:
特征:
函数对象通常可以内联
缺点:
函数对象在运行时不能与其他函数对象类型交换(至少除非它扩展了一些基类,因此会产生一些开销)
用来代替虚函数:
特征:
函数对象(非虚拟)不需要vtable和运行时调度,因此在大多数情况下它更有效
缺点:
函数对象在运行时不能与其他函数对象类型交换(至少除非它扩展了一些基类,因此会产生一些开销)
就像其他人提到的那样,仿函数就是一个像函数一样的对象,即它重载了函数调用操作符.
函数通常用于STL算法.它们很有用,因为它们可以在函数调用之前和之间保持状态,就像函数式语言中的闭包一样.例如,您可以定义一个MultiplyBy
将其参数乘以指定量的仿函数:
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
,除非该调用被内联并且编译器知道你总是将相同的函数传递给它,它就不能通过指针内联调用.
对于像我这样的新手:经过一番研究后,我想出了jalf发布的代码.
仿函数是一个类或结构对象,可以像函数一样"调用".这可以通过重载来实现() operator
.的() operator
(不知道其所谓的)可以采取任何数量的参数.其他运算符只取两个,即+ operator
只能取两个值(运算符每一个一个)并返回你重载的任何值.您可以在() operator
其中包含任意数量的参数,这使其具有灵活性.
要先创建一个仿函数,您需要创建一个类.然后使用您选择的类型和名称的参数为该类创建构造函数.这是由初始化列表(使用单个冒号运算符,我也是新手)的相同语句所遵循的,它使用先前声明的构造函数参数构造类成员对象.然后() operator
是超载.最后,声明您创建的类或结构的私有对象.
我的代码(我发现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
像这样定义一个仿函数:
template()(std::declval ()))> std::vector fmap(F f, const std::vector & vec) { std::vector result; std::transform(vec.begin(), vec.end(), std::back_inserter(result), f); return result; }
此功能需要std::vector
,并返回std::vector
给定功能时F
,需要一个T
和返回U
.不必在容器类型上定义仿函数,也可以为任何模板化类型定义仿函数,包括std::shared_ptr
:
template()(std::declval ()))> std::shared_ptr fmap(F f, const std::shared_ptr & p) { if (p == nullptr) return nullptr; else return std::shared_ptr(new U(f(*p))); }
下面是一个将类型转换为a的简单示例double
:
double to_double(int x) { return x; } std::shared_ptri(new int(3)); std::shared_ptr d = fmap(to_double, i); std::vector is = { 1, 2, 3 }; std::vector ds = fmap(to_double, is);
仿函数应该遵循两条定律.第一个是身份法,它规定如果函子被赋予一个身份函数,它应该与将类型函数应用于该类型相同,这fmap(identity, x)
应该是相同的identity(x)
:
struct identity_f { templateT operator()(T x) const { return x; } }; identity_f identity = {}; std::vector is = { 1, 2, 3 }; // These two statements should be equivalent. // is1 should equal is2 std::vector is1 = fmap(identity, is); std::vector 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::vectoris = { 1, 2, 3 }; // These two statements should be equivalent. // is1 should equal is2 std::vector is1 = fmap(std::bind(to_foo, std::bind(to_double, _1)), is); std::vector is2 = fmap(to_foo, fmap(to_double, is));
这是一个实际情况,我被迫使用Functor来解决我的问题:
我有一组函数(比如20个),它们都是相同的,除了每个函数在3个特定的位置调用不同的特定函数.
这是令人难以置信的浪费和代码重复.通常我会传入一个函数指针,然后在3个点中调用它.(因此代码只需要出现一次,而不是二十次.)
但后来我意识到,在每种情况下,特定功能需要一个完全不同的参数配置文件!有时2个参数,有时5个参数等.
另一种解决方案是拥有一个基类,其中特定函数是派生类中的重写方法.但我真的想要构建所有这些INHERITANCE,这样我就可以传递一个函数指针????
解决方案:所以我做的是,我创建了一个包装类("Functor"),它能够调用我需要调用的任何函数.我提前设置它(带有参数等),然后我传入它而不是函数指针.现在被调用的代码可以触发Functor,而不知道内部发生了什么.它甚至可以多次调用它(我需要它调用3次.)
就是这样 - 一个实用的例子,其中Functor被证明是一个明显而简单的解决方案,它允许我将代码重复从20个函数减少到1个.