35

Boost Signals库中,它们正在重载()运算符。为什么重载operator()?

这是C++中的约定吗?对于回调等?

我在一位同事的代码中看到了这个(他恰好是一个大的Boost粉丝)。在所有的提升善良中,这只会让我感到困惑。

任何有关此超载的原因的洞察?

+1

相关http://stackoverflow.com/questions/356950/c-functors-and-their-uses? – Konrad 2010-11-29 17:10:13

回答

100

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

下面是一个简单的仿例如:

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" 

仿函数用巨资泛型编程使用。许多STL算法都是以一种非常通用的方式编写的,因此您可以将自己的函数/函子插入到算法中。例如,std :: for_each算法允许您对范围的每个元素应用操作。它可以实现类似的东西:

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

你看,既然它是由功能参数化这个算法是非常通用的。通过使用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 

关于你对运营商的问题()超载,以及是有可能。只要遵守方法重载的基本规则(例如仅在返回类型上重载是不可能的),就可以完美地编写一个有多个括号运算符的函子。

1

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

my_functor(); 

是真的:

my_functor.operator()(); 

这是否意味着这样的:

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

可用于重载这个呢?

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

您的最后一行根本不是操作符重载。它需要是:“.operator()(int n,float f)”,这在你第一次看到它时看起来很混乱。你可以象这样的其他函数那样重载这个“函数调用操作符”,但是你不能用你指定的非操作符重载来重载它。 – altruic 2008-11-25 14:23:58

+0

@altruic,好点。只是修复它。 – JeffV 2008-11-25 14:26:43

+0

你的第二行是错误的,它实际上是“my_functor.operator()();”。 my_functor.operator()是方法引用,而第二组()表示调用。 – eduffy 2008-11-25 14:38:27

4

仿函数不是一个函数,所以你不能重载它。
虽然operator()的重载用于创建“函子” - 可以像函数一样调用的对象,但您的同事是正确的。结合预期“类似功能”参数的模板,这可能非常强大,因为对象和功能之间的区别变得模糊。

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

18

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

所以是这样的:

logger.log("Log this message"); 

变成这样:

logger("Log this message"); 
3

开始使用std::for_eachstd::find_if等更多的时候在你的代码,你会看到为什么它的方便有()运算符的重载能力。它还允许函数和任务具有明确的调用方法,这些方法不会与派生类中的其他方法的名称发生冲突。

2

函子就像函数指针一样。它们通常旨在可复制(如函数指针),并以与函数指针相同的方式进行调用。主要的好处是,当你有一个可以与模板函数一起工作的算法时,可以内联对operator()的函数调用。但是,函数指针仍然是有效的函子。

5

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

答案是一个仿函数可以有状态。考虑一个求和功能 - 它需要保持运行总量。

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

这并不能解释为什么需要隐藏它是一个对象并伪装成一个函数的事实。 – JeffV 2008-11-25 14:39:30

2

然而,我可以看到的一个优点是operator()的签名在不同类型之间的外观和行为是相同的。如果我们有一个拥有成员方法报告的类Reporter(..),然后是另一个具有成员方法write(..)的类Writer,那么如果我们想要同时使用这两个类,则必须编写适配器一些其他系统的模板组件。所有它会关心的是传递字符串或你有什么。如果不使用操作符()重载或写特殊类型的适配器,你不能做的东西一样

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

因为T有一个要求,即有它接受任何隐含浇注料为const char的成员函数称为写* (或者是const char [])。这个例子中的Reporter类没有这个,所以使用Reporter的T(模板参数)将无法编译。

不过,据我可以看到这将与不同类型的

T t; 
t("Hello world"); 

的工作,虽然,它仍然明确要求类型T有这样的运营商定义的,所以我们还是有要求的个人T. ,我不认为它与通用函数太奇怪了,因为它们通常被使用,但我更愿意看到这种行为的其他机制。在C#等语言中,您只需传入一个委托即可。我不太熟悉C++中的成员函数指针,但我可以想象你可以在那里实现相同的行为。

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

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

1

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

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

相关问题