为什么 C++函子优于具有命名方法的对象吗?
最近,我对函子感到兴奋,并在各处使用它们。然后出现了我需要函子执行两个不同操作的情况,并且我考虑向我的函子添加另一个方法(不重载 () 运算符)。我不确定这是否是不好的做法(也许你可以告诉我),但这让我思考为什么我首先使用函子而不仅仅是对象。所以我的问题是:
重载 () 运算符有什么特别之处吗?或者它在语法上比使用普通的命名方法更具吸引力吗?
更新:
首先,我知道为什么函子可能比函数指针更可取,如其他问题中所述。我想知道为什么它们比具有命名方法的对象更好。
其次,举个例子,当我想使用函子的另一种可能命名的方法时:基本上我有两个函数,一个计算称为图形分区的模块化的东西 -compute_modularity() ,和另一个计算分区变化后的模块化增益compute_modularity_gain()
。我认为我可以将这些函数作为同一函子的一部分传递到优化算法中,并将增益作为命名函数。我不只是将两个函子传递到算法中的原因是我想强制 compute_modularity_gain()
仅与 compute_modularity()
结合使用,而不是与另一个函子结合使用函子,例如compute_stability()(只能与compute_stability_gain()一起使用。换句话说,增益函数必须与其同级函数紧密耦合。如果有另一个函数我可以强制执行此限制,然后请告诉我。
I recently have got excited by functors and been using them all over the place. Then the situation arose where I needed my functor to perform two different operations and I thought about adding another method to my functor (not overloading the () operator). Whether this is bad practice or not I am not sure (perhaps you could tell me), but it got me thinking about why I am using functors in the first place and not just objects. So my question is:
Is there anything special about overloading the () operator or is it just very slightly more syntactically appealing than using normal named methods?
Update:
Firstly, I know why functors may be preferable to function pointers as explained in other questions. I want to know why they can be preferable to objects with named methods.
Secondly, as for an example of when I wanted to use another possibly named method of my functor: Basically I have two functions, one which calculates something called the modularity of a graph partition - compute_modularity()
, and another which computes the gain in modularity after some change of the partitioncompute_modularity_gain()
. I thought I could pass these functions as part of the same functor into an optimisation algorithm, with the gain as a named function. The reason I don't just pass two functors into the algorithm, is that I want to enforce that compute_modularity_gain()
is used only in conjuction with compute_modularity()
and not another functor e.g. compute_stability()
(which should only be used with compute_stability_gain()
. In other words, the gain function must be tightly coupled with its sibling function. If there is another way I can enforce this constraint then please let me know.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
重载
operator()
的原因是为了使仿函数具有与函数指针相同的调用语义——事实上,如果您愿意,您可以使用函数指针。重载
operator()
而不是使用函数有几个原因,但最重要的一个是编译器在使用函数指针时很少会优化掉间接函数调用,但它们会几乎总是优化掉operator()
调用——这就是为什么std::sort
通常击败std::qsort
的原因。造成这种情况的原因有很多复杂,但真正归结为大多数(不是?)编译器实现了删除函数指针调用的可能优化,这在现代硬件上是昂贵的。
,然后它就不再是函子了。要么传递两个函子来执行您想要的操作,要么定义一个 模板方法 类。 (您还可以使用 mixins 来实现 C++ 中的模板方法模式,而无需运行时开销 - 但维基百科文章并未涵盖这一点)(另请注意:与 C++ 模板不同,尽管如果您使用 C++ 模板,则可以涉及 C++ 模板) AOP路线)
The reason for the overload of
operator()
is to make functors have the same call semantics as function pointers -- and in fact you can use function pointers if you so desire.There are a few reasons for overloading
operator()
instead of using a function -- but the most important one is that compilers will rarely optimize away the indirect function call when using a function pointer, but they'll almost always optimize away theoperator()
call -- this is whystd::sort
usually beatsstd::qsort
.There are a whole bunch of complicated reasons for this but what it really boils down to is that most (no?) compilers implement the optimization possible of removing the function pointer call, which is expensive on modern hardware.
Then it's no longer a functor. Either pass two functors to do what you want, or define a template method class. (You could also use mixins to achieve the template method pattern in C++ without runtime overhead -- but the Wikipedia article doesn't cover this) (Note also: Not the same as C++ templates, though C++ templates can be involved if you go the AOP route)
函子背后的基本意图是将知道如何执行某种工作的代码与知道何时需要完成该工作的代码分离(典型的示例是将函子与 UI 按钮相关联)。
函子模型的一个小好处是普通的旧函数指针已经是函子。不需要额外的工作来包裹它们。我认为这是一个小好处,因为a)函数指针比包装对函数的直接调用效率稍低,b)我发现我几乎总是需要将某种形式的状态绑定到我正在包装的任何内容,即使它只是成员函数的
this
指针。一元接口的主要优点是它可以作为函子的生产者和消费者的通用语言。比如说,您可以将函子定义为都具有
invoke()
成员函数,但随后其他一些群体会决定对do()
进行标准化,而另一些人可能会这样做对于call()
。所有这些解决方案都涉及更多的打字。此外,单个“函子”上的多个成员函数从来都不是严格要求的。如果某些代码需要调用多个不同的操作,您可以简单地传递多个函子。这提供了良好的灵活性,因为操作可能是耦合的,或者它们可能完全不相关。
一个解耦的示例是需要相等比较器和哈希函数的哈希表。在这种情况下,这两个函数可能是不相关的:包装类的
operator==()
以获得相等性,并包装一个自由函数来计算哈希。一个耦合示例是发出多个不同事件的 UI 组件。单个类可能响应所有事件,或者不同的类可能响应不同的事件组。函子使选择任一模型变得容易,而需要一个为所有组件事件定义回调的“接口”则更加尴尬。如果单个对象想要以不同的方式处理来自两个组件的事件,函子也会使事情变得更加容易,因为您可以为每个组件提供一组不同的函子包装的成员函数。
最后,将现有功能包装在函子中已得到充分理解并得到 boost.bind 等库的广泛支持,同时创建实现
doX()
和doY()
的一次性类> 不是。另外,新标准添加了 lambda,这极大地简化了函子的创建。The basic intent behind a functor is to decouple the code that knows how to perform some kind of work from the code that knows when that work needs to be done (the classic example is associating a functor with a UI button).
One minor benefit of the functor model is that plain old function pointers are already functors. No extra work is required to wrap them. I consider this a minor benefit because a) a function pointer is slightly less efficient than wrapping a direct call to the function, and b) I find that I almost always need to bind some form of state to whatever I'm wrapping, even if it's just the
this
pointer of a member function.The key advantage of a unary interface is that it serves as a lingua franca for producers and consumers of functors. You could, say, define functors to all have an
invoke()
member function, but then some other crowd would decide to standardise ondo()
, and yet another might go forcall()
. And all of these solutions involve more typing.Also, multiple member functions on a single "functor" are never strictly required. If some code needs to invoke multiple distinct operations, you can simply pass multiple functors. This provides good flexibility, since the operations may be coupled, or they may be completely unrelated.
A decoupled example is a hash table that needs an equality-comparator and a hash function. In this case, the two functions could be unrelated: wrap the class's
operator==()
for equality and wrap a free function to compute the hash.A coupled example is a UI component that emits several distinct events. A single class might respond to all the events, or different classes might respond to different groups of events. Functors make it easy to choose either model, whereas requiring a single "interface" that defines callbacks for all the component's events is more awkward. Functors also make it much easier if a single object wants to handle events from two components differently, since you can give each component a different set of functor-wrapped member functions.
Finally, wrapping existing functionality in a functor is well understood and widely supported by libraries such as boost.bind, whereas creating throw-away classes that implement
doX()
anddoY()
isn't. Plus, the new standard adds lambdas, which dramatically simplify the creation of functors.函子唯一的特别之处在于它们可以像函数一样使用。
然而,函子也可以通过其构造函数注入信息。
您可能还想研究 std::function (如果您的编译器尚不支持,则使用 boost::function ),它可用于调整具有匹配调用签名的任何类型的对象。
std::bind 或 boost::bind 允许您将具体参数与函数的参数关联起来,这与通过仿函数的构造函数传递它们具有相同的效果。您甚至可以使用 bind 提供指向成员函数的 this 指针,以便可以像普通仿函数一样调用它们,而无需显式指定对象。
The only thing special about functors is that they can be used in the same way as functions.
However functors may also have information injected through their constructor.
You might also want to look into std::function (or boost::function if your compiler does not support it yet) that can be used to adapt any type of object with a matching call signature.
std::bind or boost::bind lets you associate concrete arguments with a function's parameters, this allows for the same effect as passing them in through the constructor of a functor. You can even use bind to provide the this pointer to member functions so that they can be called in the same way as a plain functor, without specifying an object explicitly.