实现功能作为函数对象的优缺点

12

我正在与同事讨论一个只有一个公共方法的简单类的API问题。我最初采用的是:

class CalculateSomething
{
public:
  void operator()(const SomeObject &obj) const;

private:
  // ...
}
然而,我的同事反对使用operator(),希望仅仅将该方法命名为'calculate'以增加清晰度。尽管我并不认为这个论点有说服力,但它让我考虑了一下利弊。

operator()的优点

  • 该类精简且具有单一明确的目的。一旦实例化,它基本上就像自由函数一样运作。
  • 它是一个仿函数,并且可以轻松地用作这样的函数(例如STL算法)。
  • 在使用它与范围算法时,对象可以直接传递,而不是通过函数指针,这使得编译器可以内联代码。虽然不能保证,但通过函数指针传递它完全阻止了这种可能性。

operator()的缺点

  • 没有查看类名就不太清楚该方法的作用。(我个人不同意,因为该类只有一个方法,因此从类名中就可以清楚了解其含义)
  • STL中的大多数仿函数都被期望是无状态的。我认为这是阻碍我的主要原因...

我惊讶地发现我的搜索并没有带来太多的结果,因为我认为这是相当常见的情况(一个类,一个责任)。因此,我真的很想听听其他人对此的看法。


3
顺便提一下,你可能需要添加构造函数来初始化私有成员。而Lambda表达式大多等同于此。 - Jarod42
阅读了该帖子后,似乎答案涉及Java特定的实现细节(例如,在Java中没有免费函数)。 一些涉及C ++,就此而言,我仍然没有看到任何令人信服的论点,大多数是“等同于”类型的答案。 我特别感兴趣的是优缺点,而不是“没有状态和单个公共函数的类等同于命名空间中的自由函数”。 - dgrine
@OnMyLittleDuck:构造函数使前提不准确,因为您随后具有操作符+构造函数公共,但实际上,这只是细节。Lambda可以具有状态(通过捕获),甚至可以是可变的。但它不能在此捕获上具有getter(但正如您所说,只有一个公共方法,所以 :))。 - Jarod42
将其通过函数指针传递完全阻止了这种可能性。这种说法早已不再正确。你上次查看生成的汇编代码是什么时候? - Maxim Egorushkin
@MaximEgorushkin 谢谢您指出这一点。这正是我想了解的信息类型。然而,我不明白为什么传递函数指针可以内联函数本身。毕竟,所指向的函数只能在运行时知道。我唯一能想到的情况是编译器确定函数指针的值。但也许我完全错了? - dgrine
显示剩余3条评论
2个回答

3
如果lambda表达式真的不是一个选项,那么你的选择应取决于对象承担的工作范围...当然还要考虑你的编码约定或样式。你可以决定明确表述(参见Werolik的答案),如果该方法相对陌生且需要状态,则这是一件好事,但是

让我们从标准库中看一些简单的用例...

  • std::hash:这是一个完成一项工作的函数对象,据说它做得很好,没有异议
  • 还有更多... 包括std::less及其各种组合

所有这些的共同点是它们都是动词... 在我看来,如果你的类恰好像你发布的代码片段一样,CalculateSomething对我来说表示一个动作,因此我总是可以将其实例化为CalculateSomething()(my_object...)

正如你引用的那样,它在使用STL算法本身和许多其他C++库时非常方便。 如果你采用你的同事的方法,你可能不得不使用std::binds和lambda表达式,因为你想要“适应”一个接口。

示例:

class CalculateSomething
{
    public:
         void operator()(const SomeObject &obj) const;
    private:
         // ...
}

class CalculateNothing
{
    public:
         void calculate(const SomeObject &obj) const;
    private:
         // ...
}

一个使用示例是:
std::for_each(container.begin(), container.end(), CalculateSomething());

反对
std::for_each(container.begin(), container.end(), [ c = CalculateNothing()](auto x) { c.calculate(x); });

我认为我更喜欢前者。

这确实是我的推理方式。唯一阻碍我的事情就是“状态”的问题。所以更多的是自动计算 = CalculateSomething(config); calculate(myObject)。你看到任何问题了吗?或者你仍然更喜欢这种方法(为什么)?谢谢。 - dgrine
如果CalculateSomething在状态方面不是一个轻量级的类,我认为显式方法可能更好...除此之外,我仍然更喜欢这个。 - WhiZTiM

1

好的,以下是我的5分建议。

首先,如果可能的话,我会简单地使用lambda或自由函数。你确定需要一个类来满足你的需求吗?

无论如何,假设需要使用类。

  1. 我不太喜欢使用operator()。也许可以加上注释。
  2. 可能最好将其重命名为“SomethingCalculator”——它不是一个对象吗?
  3. 然后你可以添加一个方法,比如“DoWork”,“Calculate”或其他名称。

这将使其更加明确。

更新。以上所有内容仍然可以争论,但我真正相信的是,添加足够的代码文档将产生真正的差异,而与方法名称无关。


抱歉,这个类的名称更接近于SomethingCalculator。不能使用lambda的原因是该对象在构造时保持一些状态并进行一些准备计算。然后,每次调用'operator()'或'calculate'时都会使用此信息。 - dgrine
你确定 Run()DoWork()Calculate()operator() 更有意义吗? - Jarod42
@OnMyLittleDuck,Jarod42,让我们考虑它不能是无状态的(这是另一个讨论的话题),必须是一个对象。我仍然更喜欢为operator()指定显式名称,因为当你看到它时,你立即就能理解你正在处理一个对象。基本上就是这样。无论如何,你不应该对这些事情“教条主义” - 做最适合情况和/或项目编码约定的事情。哦,还要写注释。 - Werolik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接