为什么重载operator()?

在Boost Signals库中,它们正在重载()运算符。

这是C ++的惯例吗? 对于callback等?

我已经看到了一个同事的代码(谁是一个很大的助推器粉丝)。 在所有的提升善良中,这只会导致我的困惑。

任何有关这种超负荷的理由?

重载operator()的主要目标之一是创build函数。 一个仿函数就像一个函数,但它的优点是它是有状态的,这意味着它可以保持数据在调用之间反映它的状态。

这是一个简单的仿函数的例子:

struct Accumulator { int counter = 0; int operator()(int i) { return counter += i; } } ... Accumulator acc; cout << acc(10) << endl; //prints "10" cout << acc(20) << endl; //prints "30" 

函子大量使用generics编程。 许多STLalgorithm是以非常一般的方式编写的,所以你可以将你自己的函数/函子插入到algorithm中。 例如,std :: for_eachalgorithm允许您对范围的每个元素应用一个操作。 可以这样实现:

 template <typename InputIterator, typename Functor> void for_each(InputIterator first, InputIterator last, Functor f) { while (first != last) f(*first++); } 

你会发现这个algorithm是非常通用的,因为它是通过函数进行参数化的。 通过使用operator(),可以使用函数或函数指针。 下面是一个显示两种可能性的例子:

 void print(int i) { std::cout << i << std::endl; } ... std::vector<int> vec; // Fill vec // Using a functor Accumulator acc; std::for_each(vec.begin(), vec.end(), acc); // acc.counter contains the sum of all elements of the vector // Using a function pointer std::for_each(vec.begin(), vec.end(), print); // prints all elements 

关于你的operator()重载的问题,是的,这是可能的。 只要你尊重方法重载的基本规则(例如只在返回types上重载是不可能的),你可以完美地写一个有几个括号运算符的函子。

它允许一个类像一个函数一样行事。 我曾经在一个日志类中使用它,这个调用应该是一个函数,但是我想要这个类的额外好处。

所以像这样的东西:

 logger.log("Log this message"); 

变成这样:

 logger("Log this message"); 

一个函子不是一个函数,所以你不能超载它。
你的同事是正确的,尽pipeoperator()的重载被用来创build“函子” – 可以被称为函数的对象。 结合期待“函数式”参数的模板,这可能是相当强大的,因为对象和函数之间的区别变得模糊。

正如其他海报所说的:函子比普通函数具有优势,因为它们可以具有状态。 这个状态可以用于一次迭代(例如计算容器中所有元素的总和)或者多次迭代(例如,查找满足特定条件的多个容器中的所有元素)。

在你的代码中更经常地使用std::for_eachstd::find_if等等,你会明白为什么有能力重载()运算符。 它也允许函子和任务有一个明确的调用方法,不会与派生类中的其他方法的名称冲突。

许多人回答说,它是一个函子,没有告诉一个原因为什么一个函数比一个普通的旧函数更好的原因。

答案是一个仿函数可以有状态。 考虑一个总结function – 它需要保持一个总计。

 class Sum { public: Sum() : m_total(0) { } void operator()(int value) { m_total += value; } int m_total; }; 

您也可以查看C ++ faq的Matrix示例 。 这样做有很好的用处,但这当然取决于你想要完成什么。

函子基本上就像函数指针一样。 它们通常是可复制的(就像函数指针一样),并以与函数指针相同的方式被调用。 主要的好处是,当你有一个与模板仿函数一起工作的algorithm时,可以内联调用operator()的函数。 但是,函数指针仍然是有效的函子。

在C ++中使用operator()来形成函子是与函数式编程范例相关的,这些范式通常使用类似的概念: 闭包 。

我可以看到的一个优点是,可以讨论的是operator()的签名在不同的types中performance相同。 如果我们有一个拥有成员方法报告(。)的类Reporter,然后是另一个具有成员方法write(..)的类Writer,那么如果我们想要同时使用这两个类,则必须编写适配器一些其他系统的模板组件。 所有它会关心的是传递string或你有什么。 如果没有使用operator()重载或编写特殊types的适配器,你就不能做类似的东西

 T t; t.write("Hello world"); 

因为T有一个要求,有一个成员函数称为写,它接受任何隐式转换为const char *(或者说是const char [])。 这个例子中的Reporter类没有这个,所以把T(一个模板参数)作为Reporter的话,将不能编译。

但是,据我所知,这可以适用于不同的types

 T t; t("Hello world"); 

尽pipe如此,它仍然明确要求typesT有这样一个操作符定义,所以我们仍然对T有一个要求。就个人而言,我不认为它与常用的函子太奇怪,但我宁愿看到其他机制这种行为。 在像C#这样的语言中,你可以传入一个委托。 我不太熟悉C ++中的成员函数指针,但我可以想象你可以在那里实现相同的行为。

除了合成糖的行为,我真的没有看到运算符重载执行这些任务的优势。

我相信有更多的知情人,比我有更好的理由,但我想我会为你们其余的分享我的意见。

另一位同事指出,这可能是一种将函数对象伪装成函数的方法。 例如,这个:

 my_functor(); 

是真的:

 my_functor.operator()(); 

那么这是否意味着这一点:

 my_functor(int n, float f){ ... }; 

也可以用来超载这个呢?

 my_functor.operator()(int n, float f){ ... }; 

其他职位已经做了很好的描述operator()如何工作,为什么它可以是有用的。

我最近一直在使用一些代码,使得运算符()的使用非常广泛。 重载这个操作符的一个缺点就是一些IDE成为不太有效的工具。 在Visual Studio中,通常可以右键单击方法调用以转到方法定义和/或声明。 不幸的是,VS不够聪明,以索引operator()调用。 特别是在覆盖整个运算符()的复杂代码中,可能很难找出在哪里执行代码。 在几种情况下,我发现我必须运行代码并通过它来追踪实际运行的内容。