【问题标题】:C++ functor advantage - holding the state [duplicate]C ++函子优势 - 持有状态[重复]
【发布时间】:2014-11-30 01:10:06
【问题描述】:

我确实研究了函子的整个概念,不幸的是我无法理解函子相对于典型函数的真正优势。

根据一些学术脚本,函子可以保持与函数不同的状态。 谁能用一些简单易懂的例子来详细说明这一点?

我真的不明白为什么典型的常规功能不能做到这一点。对于这种新手问题,我真的很抱歉。

【问题讨论】:

  • 不确定我是否同意这里的近距离投票。这是一个合法的编程问题。
  • 阅读here
  • 这是 C++ 的缺陷;并非所有语言都受此影响。
  • 假设您要计算从特定位置调用函数的次数。您可以在函数内放置一个静态变量,但这将计算所有调用并且无法重置。相反,您可以创建一个以计数作为成员变量的函数类,并为每个感兴趣的调用站点创建一个不同的函数对象。
  • @molbdnilo 你能解释一下吗?

标签: c++ functor


【解决方案1】:

作为一个非常简单的演示,让我们考虑一个快速排序。我们选择一个值(通常称为“枢轴”)并将输入集合分为比较小于枢轴的值和大于或等于枢轴1的值。

标准库已经有std::partition 可以自己进行分区——将集合分成满足指定条件的项和不满足指定条件的项。因此,要进行分区,我们只需要提供一个合适的谓词即可。

在这种情况下,我们需要一个简单的比较,例如:return x < pivot;。但是,每次都传递枢轴值变得困难。 std::partition 只是传递集合中的一个值并询问:“这是否通过了您的测试?”您无法告诉std::partition 当前的枢轴值是什么,并让它在调用时将其传递给您的例程。当然,可以完成(例如,Windows 中的许多枚举函数都以这种方式工作),但它变得相当笨拙。

当我们调用std::partition 时,我们已经选择了枢轴值。我们想要的是一种方法......将该值绑定到将传递给比较函数的参数之一。一种非常丑陋的方法是通过全局变量“传递”它:

int pivot;

bool pred(int x) { return x < pivot; }

void quick_sort(int *begin, int *end) { 
    if (end - begin < 2)
        return;

    pivot = choose_pivot(begin, end);

    int *pos = std::partition(begin, end, pred);
    quick_sort(begin, pos);
    quick_sort(pos, end);
}

真的希望我不必指出,如果我们能提供帮助,我们宁愿不使用全局变量。避免它的一种相当简单的方法是创建一个函数对象。我们在创建对象时传递当前枢轴值,并将该值作为状态存储在对象中:

class pred { 
    int pivot;
public:
    pred(int pivot) : pivot(pivot) {}

    bool operator()(int x) { return x < pivot; }
};

void quick_sort(int *begin, int *end) { 
    if (end-begin < 2)
        return;

    int pivot = choose_pivot(begin, end);

    int *pos = std::partition(begin, end, pred(pivot));        
    quick_sort(begin, pos);
    quick_sort(pos, end);
}

这增加了一点额外的代码,但作为交换,我们取消了一个全局的——一个相当合理的交换。

当然,从 C++11 开始,我们还可以做得更好——该语言添加了“lambda 表达式”,可以为我们创建一个非常相似的类。使用它,我们的代码看起来像这样:

void quick_sort(int *begin, int *end) { 
    if (end-begin < 2)
        return;

    int pivot = find_pivot(begin, end);

    auto pos = std::partition(begin, end, [pivot](int x) { return x < pivot; });
    quick_sort(begin, pos);
    quick_sort(pos, end);
}

这改变了我们用来指定类/创建函数对象的语法,但它仍然与前面的代码几乎相同的基本思想:编译器生成一个带有构造函数和operator()。我们用方括号括起来的值被传递给构造函数,(int x) { return x &lt; pivot; } 基本上成为该类的operator() 的主体2

这使得代码更容易编写更容易阅读——但它并没有改变我们正在创建一个对象的基本事实,在构造函数中“捕获”一些状态,并使用重载的operator() 进行比较。

当然,比较恰好是我们在进行排序之类的事情时所需要的。它更普遍地使用 lambda 表达式和函数对象,但我们当然不限于此。再举一个例子,让我们考虑“标准化”一个双打集合。我们想找到最大的一个,然后将集合中的每个值除以那个,所以每个项目都在 0.0 到 1.0 的范围内,但所有项目都保持与以前相同的比率:

double largest = * std::max_element(begin, end);
std::for_each(begin, end, [largest](double d) { return d/largest; });

这里我们再次使用几乎相同的模式:创建一个存储一些相关状态的函数对象,然后重复应用该函数对象的 operator() 来完成真正的工作。


  1. 我们可以分为小于或等于和大于。或者我们可以创建三个组:小于、等于、大于。后者可以在存在许多重复的情况下提高效率,但目前我们真的不在乎。
  2. 关于 lambda 表达式的知识远不止这些——我正在简化一些事情,而完全忽略了我们目前不关心的其他事情。

【讨论】:

    猜你喜欢
    • 2020-04-07
    • 2017-10-25
    • 2020-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-15
    • 2015-06-22
    • 1970-01-01
    相关资源
    最近更新 更多