我最近对仿函数感到兴奋并且一直在使用它们。然后情况出现了,我需要我的仿函数执行两个不同的操作,我想到为我的仿函数添加另一个方法(不重载()运算符)。这是不是不好的做法我不确定(也许你可以告诉我),但它让我思考为什么我首先使用仿函数而不仅仅是对象。所以我的问题是:
重载()运算符有什么特别之处,还是比使用普通的命名方法更具语法吸引力?
更新
首先,我知道为什么函子可能比其他问题中解释的更好。我想知道为什么它们可以优于具有命名方法的对象。
其次,至于我何时想要使用另一个可能命名的仿函数方法的例子:基本上我有两个函数,一个计算图形分区的模块性 - compute_modularity()
,另一个在对分区compute_modularity_gain()
进行一些更改后,计算模块化的增益。我以为我可以将这些函数作为同一仿函数的一部分传递给优化算法,并将增益作为命名函数。我不仅仅将两个仿函数传递给算法的原因是我要强制compute_modularity_gain()
仅与compute_modularity()
结合使用而不是另一个仿函数,例如compute_stability()
(只应与compute_stability_gain()
一起使用。换句话说,增益函数必须与其兄弟函数紧密结合。如果还有其他方法可以强制执行此约束,请告诉我。
答案 0 :(得分:6)
operator()
重载的原因是让函子具有与函数指针相同的调用语义 - 事实上,如果你愿意,你可以使用函数指针。
重载operator()
而不是使用函数有几个原因 - 但最重要的是编译器在使用函数指针时很少会优化间接函数调用,但它们几乎总是优化operator()
来电 - 这就是为什么std::sort
通常胜过std::qsort
。
这有很多复杂的原因,但它真正归结为大多数(没有?)编译器实现了删除函数指针调用的优化,这在现代硬件上很昂贵。
然后出现了我需要我的仿函数执行两个不同操作的情况
然后它不再是一个仿函数。要么通过两个仿函数来做你想做的事,要么定义一个template method类。 (您也可以使用mixins在C ++中实现模板方法模式而不需要运行时开销 - 但维基百科文章没有涉及到这一点)(注意:与C ++模板不同,但如果你去了C ++模板可能会涉及到AOP路线)
答案 1 :(得分:1)
仿函数背后的基本意图是从知道何时需要完成工作的代码中解耦知道如何执行某种工作的代码(经典示例是将仿函数与UI按钮相关联)。 / p>
仿函数模型的一个小好处是普通的旧函数指针已经是函子。包装它们不需要额外的工作。我认为这是一个小小的好处,因为a)函数指针的效率比包装对函数的直接调用效率稍低,而b)我发现我几乎总是需要将某种形式的状态绑定到我正在包装的任何状态,即使它只是成员函数的this
指针。
一元界面的主要优点是它可以作为仿函数的生产者和消费者的通用语言。你可以说,定义仿函数都有一个invoke()
成员函数,但是其他一些人会决定在do()
上进行标准化,而另一个人可能会考虑call()
。所有这些解决方案都涉及更多的打字。
此外,从不严格要求单个“仿函数”上的多个成员函数。如果某些代码需要调用多个不同的操作,则可以简单地传递多个函子。这提供了良好的灵活性,因为操作可以耦合,或者它们可能完全不相关。
解耦示例是需要等式比较器和散列函数的散列表。在这种情况下,这两个函数可能是不相关的:将类的operator==()
包装为相等,并包装一个自由函数来计算散列。
耦合示例是一个发出几个不同事件的UI组件。单个类可能会响应所有事件,或者不同的类可能会响应不同的事件组。 Functors可以很容易地选择任何一个模型,而需要一个“接口”来定义所有组件事件的回调更加尴尬。如果单个对象想要以不同方式处理来自两个组件的事件,Functors也会更容易,因为您可以为每个组件提供一组不同的函子包装成员函数。
最后,将函数中的现有功能包装起来得到了很好的理解,并得到了诸如boost.bind等库的广泛支持,而创建实现doX()
和doY()
的丢弃类却没有。此外,新标准添加了lambdas,大大简化了仿函数的创建。
答案 2 :(得分:0)
关于仿函数的唯一特别之处在于它们可以像函数一样使用。 但是,仿函数也可能通过构造函数注入信息。
您可能还想查看std :: function(或者如果您的编译器还不支持它的boost :: function),它可用于使任何类型的对象适应匹配的调用签名。
std :: bind或boost :: bind允许您将具体参数与函数的参数相关联,这允许与通过仿函数的构造函数传递它们相同的效果。你甚至可以使用bind来提供指向成员函数的this指针,这样就可以像普通仿函数一样调用它们,而无需显式指定对象。