为什么我应该更喜欢单独的函数而不是静态方法来进行 C++ 中的函数式编程?

Why should I prefer a separate function over a static method for functional programming in c++?

在我的另一个问题 中,人们解决了我的实际问题,然后建议我采用不同的方法来遵循最佳实践。我想更好地理解原因。

这个例子使用了一个比较器,但我真的很想知道是否有函数式编程的一般规则(例如,也许还有与 class 等相关的聚合函数)

比较器功能的示例用例:

#include <set>

int main() {
    std::set<MyClass, decltype(SOMETHING_HERE)> s(SOMETHING_HERE);
    return 0;
}

以下是我知道的解决方案,从最推荐到最不推荐(我相信)- 与我目前选择的顺序相反。

1.

使用函子的推荐方式(我相信):

struct MyComparator {
    bool operator() (MyClass a, MyClass b) const {
        return a > b; // Reversed
    }
};
std::set<MyClass, MyComparator)> s;

我不喜欢:冗长;在词汇上与 MyClass 无关;我不明白为什么 s 没有构造函数参数。我对缺少参数感到困惑,因为 set 似乎混合了 up/combine 在编译时/使用类型系统(如本示例)和运行时/使用指针来决定比较算法。如果它只是一种方式或另一种方式,我会接受“就是这样”。

2.

一个对我来说更好的方法:

auto const my_comparator = [](int a, int b) {
    return a > b;
};
std::set<int, decltype(my_comparator)> s(my_comparator);

它更简洁。意图很明确:它是一个函数,而不是可以添加的 class 。传递对象,而不仅仅是类型,对我来说很有意义(不能有 2 种不同的实现)。

3.

对我来说最有意义的方法(基于其他语言):

class MyClass {
public:
    
    // More code here
    
    static auto compare_reverse(MyClass a, MyClass b) {
        return a > b;
    }
};
std::set<int, decltype(&MyClass::compare_reverse)> s(&MyClass::compare_reverse);

它显然与 MyClass 相关(并且在词汇上与之相关)。看起来高效(虽然有人告诉我它不是)和简洁。


任何解释为什么推荐的顺序更好,性能,bugs/maintainability,哲学,非常感谢。

首先,请注意,如果您只想要一个 > 比较器,则有 std::greater<> and std::greater<MyClass> (the former is usually superior).


正在查看您列出的选项:

(1)

struct MyComparator
{
    bool operator()(MyClass a, MyClass b) const
    {
        return a > b;
    }
};
std::set<MyClass, MyComparator)> s;

正如你所说,这是推荐的方式。

not lexically tied to MyClass

按照评论里的建议,可以放在MyClass.

里面

don't understand why s has no constructor argument

如果您不将比较器传递给构造函数,则比较器是默认构造的(如果它是默认构造的,否则会出现编译错误)。

您可以手动传递 MyComparator{},但没有意义。

(2)

auto my_comparator = [](int a, int b)
{
    return a > b;
};
std::set<int, decltype(my_comparator)> s(my_comparator);

它完全等同于 (1),只是您必须将 my_comparator 传递给构造函数。 C++20 通过使 lambdas 可以默认构造(如果它们没有捕获)来解决这个问题,使参数变得不必要。

(3)

class MyClass
{
  public:
    static auto compare_reverse(MyClass a, MyClass b)
    {
        return a > b;
    }
};
std::set<int, decltype(&MyClass::compare_reverse)> s(&MyClass::compare_reverse);

这为您提供了一个额外的功能:您可以在运行时通过将不同的函数传递给构造函数来选择比较器。

此功能带来开销:集合存储指向函数的指针(通常为 4 或 8 个字节),并且在进行比较时必须查看指针。

(3.5)

您可以将函数包装在 std::integral_constant 中。结果等同于 (1).

bool foo(int x, int y) {return x > y;}

std::set<int, std::integral_constant<decltype(&foo), foo>> s;
// Or:
using MyComparator = std::integral_constant<decltype(&foo), foo>;
std::set<int, MyComparator> s;

如果您正在处理一个现有类型,它将比较器作为一个函数提供,并且您不希望 (3) 的开销,那么这很好。


所有这些(除了 (3))都是等价的。

如果std::greater<>适用,你应该使用它。

否则我会推荐 (1) 作为最不容易混淆的选项。