为什么要重写 operator()?
为什么要重写operator()?
很多人回答说这样可以创建一个函数对象(functor),但并没有解释为什么函数对象比普通函数更好的一个重要原因。
原因是函数对象可以拥有状态。考虑一个求和函数 - 它需要保持一个累加的总和。
class Sum { public: Sum() : m_total(0) { } void operator()(int value) { m_total += value; } int m_total; };
这并不解释为什么需要隐藏函数对象的事实,伪装成一个函数。
Jeff V: 方便性。这意味着无论我们调用一个函数对象还是函数指针,都可以使用相同的语法进行调用。例如,如果你看一下std::for_each,它可以同时使用函数对象或函数指针,因为在这两种情况下,调用的语法都是相同的。
为什么要重载operator()?
重载operator()的主要目标之一是创建一个函数对象(functor)。函数对象的行为就像一个函数,但它具有保持状态的优势,也就是说它可以在调用之间保持反映其状态的数据。
函数对象在泛型编程中被广泛使用。许多STL算法都以非常通用的方式编写,以便您可以将自己的函数/函数对象插入算法中。例如,算法std::for_each允许您对范围中的每个元素应用一个操作。它可以实现如下:
template
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
// 填充vec
// 使用函数对象
Accumulator acc;
std::for_each(vec.begin(), vec.end(), acc);
// acc.counter 包含向量的所有元素的总和
// 使用函数指针
std::for_each(vec.begin(), vec.end(), print); // 打印所有元素
关于您关于operator()重载的问题,是的,这是可能的。只要遵守方法重载的基本规则(例如,不可能仅根据返回类型进行重载),您可以编写具有多个圆括号运算符的函数对象。
我认为这个问题的大部分答案是STL的语法。通过使用operator()作为函数对象的操作部分,它可以很好地与STL配合使用。
另一个(通常较小的)函数对象相比于函数的优点是它们可以被轻松内联。没有涉及指针间接访问,只是在类上调用一个(非虚拟)成员函数,因此编译器可以确定调用哪个函数,并内联它。
不再重复为什么选择operator(),因为您已经编辑到帖子中了 🙂
'prints "10 30"':实际上,它可能会打印"20 30"。子表达式的评估顺序没有指定,除了要求每个<<的操作数在该<<之前被评估(因此,由于<<的关联方式,已定义打印部分的顺序)。没有规定最后一个<<的操作数必须在第一个<<的操作数之后评估,只要最后一个<<本身在第一个<<之后评估即可。编译器可以提前完成acc(20)的计算。
operator()也是一个函数吗?那么它如何比其他任何函数更具内联优势?它可能会有任何区别只是一种惯例;成员函数通常被写成内联,而非成员函数则不是。如果您知道将在需要内联的上下文中使用自由函数,则可以轻松更改。
是的,它确实是。但是,请考虑编译器在上面示例中的for_each中看到的内容。如果我将一个自由函数传递给它作为第三个参数,那么它将为类型int(*)(int)实例化for_each。在编译时,这并不能告诉我们要调用什么函数。它告诉我们在运行时我们将获得一个指针,并且该指针将告诉我们要调用哪个函数。编译器不知道将调用哪个函数,因此无法内联调用。
然而,如果您传递一个函数对象,比如上面定义的Accumulator,那么for_each将为Accumulator实例化,并且在其内部调用的函数是Accumulator::operator()(int)。因此,编译器知道将调用哪个函数,而不管在运行时传递的实际值是什么。这使得它可以轻松地内联调用。