【发布时间】:2010-09-23 23:10:03
【问题描述】:
在Boost Signals 库中,它们重载了 () 运算符。
这是 C++ 中的约定吗?对于回调等?
我在一位同事的代码中看到了这一点(他恰好是 Boost 的忠实粉丝)。在所有 Boost 的优点中,这只会让我感到困惑。
关于这种过载的原因有什么见解吗?
【问题讨论】:
标签: c++ boost operator-overloading functor function-call-operator
在Boost Signals 库中,它们重载了 () 运算符。
这是 C++ 中的约定吗?对于回调等?
我在一位同事的代码中看到了这一点(他恰好是 Boost 的忠实粉丝)。在所有 Boost 的优点中,这只会让我感到困惑。
关于这种过载的原因有什么见解吗?
【问题讨论】:
标签: c++ boost operator-overloading functor function-call-operator
重载 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
关于您关于 operator() 重载的问题,是的,这是可能的。只要您遵守方法重载的基本规则(例如,仅在返回类型上重载是不可能的),您就可以完美地编写一个具有多个括号运算符的仿函数。
【讨论】:
Accumulator,那么for_each 将被实例化为Accumulator,并且在其主体中调用的函数是@ 987654327@。因此编译器知道将调用哪个函数,而不管在运行时传入的实际值如何。这允许它简单地内联调用
其他帖子很好地描述了 operator() 的工作原理以及它为何有用。
我最近一直在使用一些非常广泛地使用 operator() 的代码。重载此运算符的一个缺点是某些 IDE 会因此成为不太有效的工具。在 Visual Studio 中,您通常可以右键单击方法调用以转到方法定义和/或声明。不幸的是,VS 不够聪明,无法索引 operator() 调用。尤其是在复杂的代码中,到处都被覆盖了 operator() 定义,很难弄清楚哪段代码在哪里执行。在某些情况下,我发现我必须运行代码并跟踪它才能找到实际运行的内容。
【讨论】:
另一位同事指出,这可能是一种将仿函数对象伪装成函数的方法。例如,这个:
my_functor();
真的是:
my_functor.operator()();
这是否意味着:
my_functor(int n, float f){ ... };
也可以用来重载这个吗?
my_functor.operator()(int n, float f){ ... };
【讨论】:
我可以看到的一个优点,但是可以讨论,是 operator() 的签名在不同类型中看起来和行为相同。如果我们有一个类 Reporter,它有一个成员方法 report(..),然后是另一个类 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++ 中的成员函数指针不太熟悉,但我可以想象你也可以在那里实现相同的行为。
除了语法糖行为之外,我并没有真正看到运算符重载来执行此类任务的优势。
我确信有更多明知故犯的人比我有更好的理由,但我想我会提出我的意见供大家分享。
【讨论】:
仿函数不是函数,因此不能重载它。
尽管 operator() 的重载用于创建“函子”——可以像函数一样调用的对象,但你的同事是正确的。结合期望“类函数”参数的模板,这可能非常强大,因为对象和函数之间的区别变得模糊。
正如其他发帖者所说:函子比普通函数的优势在于它们可以拥有状态。此状态可用于单次迭代(例如计算容器中所有元素的总和)或多次迭代(例如查找多个容器中满足特定条件的所有元素)。
【讨论】:
在 C++ 中使用 operator() 形成 functors 与 functional programming 范例有关,后者通常使用类似的概念:closures。
【讨论】:
许多人回答说它是一个函子,但没有说明函子比普通旧函数更好的一个重要原因。
答案是函子可以有状态。考虑一个求和函数 - 它需要保持一个运行总计。
class Sum
{
public:
Sum() : m_total(0)
{
}
void operator()(int value)
{
m_total += value;
}
int m_total;
};
【讨论】:
函子基本上就像函数指针。它们通常旨在可复制(如函数指针)并以与函数指针相同的方式调用。主要的好处是,当您有一个使用模板仿函数的算法时,可以内联对 operator() 的函数调用。但是,函数指针仍然是有效的函子。
【讨论】:
开始在您的代码中更频繁地使用std::for_each、std::find_if 等,您就会明白为什么能够重载 () 运算符会很方便。它还允许函子和任务有一个明确的调用方法,不会与派生类中其他方法的名称冲突。
【讨论】:
它允许一个类像一个函数一样工作。我在一个日志类中使用了它,调用应该是一个函数,但我想要这个类的额外好处。
所以是这样的:
logger.log("Log this message");
变成这样:
logger("Log this message");
【讨论】:
您也可以查看C++ faq's Matrix example。这样做有很好的用途,但这当然取决于您要完成的工作。
【讨论】: