C ++ functor优势 - 持有状态

时间:2014-10-05 16:58:45

标签: c++ functor

我确实研究了仿函数的整个想法,遗憾的是我无法理解仿函数相对于典型函数的真正优势。

根据一些学术脚本,仿函数可以保持状态不同于函数。 任何人都可以用一些简单易懂的例子来详细说明这个吗?

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

1 个答案:

答案 0 :(得分:3)

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

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

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

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

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开始,我们可以做得更好 - 添加语言&#34; lambda表达式&#34;这可以创建一个类似于我们的类。使用它,我们的代码看起来像这样:

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 < pivot; }基本上成为该类 2 operator()的主体。

这使代码更容易编写更容易阅读 - 但它并没有改变我们创建对象的基本事实,&#34;捕获& #34;构造函数中的某个状态,并使用重载的operator()进行比较。

当然,比较恰好是排序等事情所需要的。 是更常见的lambda表达式和函数对象的常见用法,但我们当然不限于此。再举一个例子,让我们考虑&#34;正常化&#34;双打的集合。我们想要找到最大的那个,然后除以该集合中的每个值,因此每个项目的范围在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表达的知识 - 我简化了一些事情,完全忽略了我们现在不关心的其他事情。