为什么要覆盖operator()?

时间:2008-11-25 14:07:38

标签: c++ boost operator-overloading functor function-call-operator

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

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

我在同事的代码中看到了这一点(恰好是Boost的忠实粉丝)。在那里的所有Boost善良中,这只会让我感到困惑。

有关此次超载原因的任何见解?

11 个答案:

答案 0 :(得分:130)

重载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"

Functors大量使用泛型编程。许多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

关于你关于operator()重载的问题,是的,这是可能的。只要您遵守方法重载的基本规则(例如,不能仅在返回类型上进行重载),您就可以完美地编写具有多个括号运算符的仿函数。

答案 1 :(得分:22)

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

这样的事情:

logger.log("Log this message");

变成了这个:

logger("Log this message");

答案 2 :(得分:5)

许多人回答说它是一个仿函数,没有说明为什么仿函数比普通函数更好的一个重要原因。

答案是仿函数可以拥有状态。考虑求和函数 - 它需要保持运行总计。

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

答案 3 :(得分:4)

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

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

答案 4 :(得分:3)

在您的代码中更频繁地使用std::for_eachstd::find_if等等,您将会明白为什么能够重载()运算符是很方便的。它还允许函子和任务具有一个明确的调用方法,该方法不会与派生类中其他方法的名称冲突。

答案 5 :(得分:2)

您也可以查看C++ faq's Matrix example。这样做有很好的用途,但它当然取决于你想要完成的任务。

答案 6 :(得分:2)

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

答案 7 :(得分:2)

使用operator()在C ++中形成functorsfunctional programming范例有关,这些范例通常使用类似的概念:closures

答案 8 :(得分:2)

我可以看到的一个优点是,可以讨论的是,operator()的签名在不同类型中的外观和行为相同。如果我们有一个类Reporter有一个成员方法报告(..),然后另一个类Writer,它有一个成员方法write(..),如果我们想要使用这两个类,我们将不得不编写适配器某个其他系统的模板组件。它所关心的只是传递字符串或者你有什么。如果不使用operator()重载或编写特殊类型的适配器,则无法执行

之类的操作
T t;
t.write("Hello world");

因为T要求有一个名为write的成员函数,它接受任何隐式可转换为const char *(或者更确切地说是const char [])的东西。此示例中的Reporter类没有,因此将T(模板参数)作为Reporter将无法编译。

但是,到目前为止,我可以看到这适用于不同类型的

T t;
t("Hello world");
但是,它仍然明确要求类型T具有这样的运算符定义,所以我们仍然需要T。就个人而言,我不认为它太过于使用仿函数,因为它们常用,但我宁愿看到这种行为的其他机制。在像C#这样的语言中,您只需传入一个委托即可。我不太熟悉C ++中的成员函数指针,但我可以想象你可以在那里实现相同的行为。

除了合成糖行为之外,我并没有真正看到运算符重载的优势来执行此类任务。

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

答案 9 :(得分:1)

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

my_functor();

真的:

my_functor.operator()();

这是否意味着:

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

也可以用来重载这个吗?

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

答案 10 :(得分:1)

其他帖子在描述operator()如何工作以及为什么它有用方面做得很好。

我最近使用了一些非常广泛使用operator()的代码。这种运算符重载的一个缺点是某些IDE因此变得不那么有效。在Visual Studio中,通常可以右键单击方法调用以转到方法定义和/或声明。不幸的是,VS不够智能来索引operator()调用。特别是在复杂的代码中,覆盖了整个地方的operator()定义,很难弄清楚哪些代码在哪里执行。在某些情况下,我发现我必须运行代码并通过它来查找实际运行的内容。