为什么要重写操作符() ?

加强信号库中,它们正在重载()操作符。

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

我在一个同事(碰巧是 Boost 的忠实粉丝)的代码中看到了这一点。在所有 Boost 的善意中,这只会让我感到困惑。

有什么关于超载原因的线索吗?

81528 次浏览

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

my_functor();

真的是:

my_functor.operator()();

那么,这是否意味着:

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

也可以用来超载这个吗?

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

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

函数不是函数,所以不能重载它。
您的同事是正确的,尽管操作符()的重载被用来创建“函数”——可以像函数一样调用的对象。与期望“类函数”参数的模板结合起来,这可能会非常强大,因为对象和函数之间的区别变得模糊。

正如其他发帖人所说: 函子比普通函数有一个优势,因为它们可以有状态。这种状态可以在一次迭代中使用(例如计算容器中所有元素的总和) ,也可以在多次迭代中使用(例如在多个容器中找到满足特定条件的所有元素)。

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

像这样:

logger.log("Log this message");

变成了这样:

logger("Log this message");

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

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

许多人回答说,它创造了一个函数,却没有告诉我们为什么一个函数优于一个普通的老函数的一个重要原因。

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

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

重载操作符()的主要目标之一是创建一个函数。函数的作用类似于函数,但它的优势在于它是有状态的,这意味着它可以在调用之间保持反映其状态的数据。

下面是一个简单的函子示例:

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++);
}

您可以看到这个算法是非常通用的,因为它是由函数参数化的。通过使用操作符() ,这个函数允许您使用函数或函数指针。下面的例子显示了这两种可能性:

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

关于操作符()重载的问题,是的,这是可能的。只要遵守方法重载的基本规则(例如,仅对返回类型重载是不可能的) ,就可以完美地编写一个包含多个括号运算符的函数。

在 C + + 中使用操作符()来形成 函子函数式程序设计范例有关,函数式程序设计范例通常使用一个类似的概念: 关闭

尽管可以讨论这一点,但我可以看到的一个优势是,操作符()的签名在不同类型之间看起来和行为是相同的。如果我们有一个类 Reporter,它有一个成员方法报告(。.),然后是另一个类 Writer,它有一个成员方法 write (。.),如果我们想使用这两个类作为其他系统的模板组件,我们必须编写适配器。它所关心的只是传递字符串或者其他什么东西。如果没有使用操作符()重载或编写特殊类型适配器,就不能进行以下操作

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

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

然而,就我目前所见,这种方法适用于不同的类型

T t;
t("Hello world");

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

除了语法糖行为之外,我实在看不出运算符重载在执行这些任务方面的优势。

我相信有更多知情的人比我有更好的理由,但我认为我应该把我的意见分享给你们其余的人。

其他一些文章很好地描述了 Operator ()是如何工作的,以及它为什么有用。

我最近一直在使用一些代码,它们非常广泛地使用了运算符()。重载此操作符的一个缺点是,一些 IDE 因此成为效率较低的工具。在 VisualStudio 中,通常可以右键单击方法调用以转到方法定义和/或声明。不幸的是,VS 不够聪明,不能索引操作员()调用。特别是在遍布重写的操作符()定义的复杂代码中,可能很难确定哪段代码在哪里执行。在一些情况下,我发现我必须运行代码并通过跟踪来找到实际运行的内容。

重载操作符()可以使类对象调用约定更加容易。

Functoruser-defined conversion function很容易混淆。

下面两个例子展示了

1. Functor
2. User-defined conversion function

1. 功能界别:

   struct A {
int t = 0;
int operator()(int i) { return t += i; } // must have return type or void
};


int main() {
A a;
cout << a(3); // 3
cout << a(4); // 7 (Not 4 bcos it maintaines state!!!)
}

2. 用户自定义转换功能:

    struct A {
int t = 3;
operator int() { return t; } // user-defined conversion function
// Return type is NOT needed (incl. void)
};


int main() {
cout << A(); // 3 - converts the object{i:3} into integer 3


A a;
cout << a;   // 3 - converts the object{i:3} into integer 3
}