我确实研究了函子的整个概念,但是不幸的是,我无法理解函子相对于典型函数的真正优势。
根据一些学术脚本,函子可以保持与功能不同的状态。任何人都可以通过一些简单易懂的示例来详细说明吗?
我真的不明白为什么典型的常规功能不能做到这一点。对于这种新手问题,我真的感到很抱歉。
作为一个非常简单的演示,让我们考虑快速排序。我们选择一个值(通常称为“主元”),并将输入集合分为比较小于主元的集合和比较大于或等于主元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);
}
Run Code Online (Sandbox Code Playgroud)
我真的希望我不必指出,如果我们可以帮助的话,我们宁愿不使用全局变量。避免这种情况的一种相当简单的方法是创建一个函数对象。我们在创建对象时传递当前的枢轴值,并将该值作为状态存储在对象中:
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);
}
Run Code Online (Sandbox Code Playgroud)
这增加了一点额外的代码,但作为交换,我们消除了全局的——一个相当合理的交换。
当然,从 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);
}
Run Code Online (Sandbox Code Playgroud)
这改变了我们用来指定类/创建函数对象的语法operator(),但它仍然与前面的代码几乎相同的基本思想:编译器生成一个带有构造函数和. 我们括在方括号中的值将传递给构造函数,并且基本上成为该类2(int x) { return x < pivot; }的主体。operator()
这使得代码更容易编写和阅读——但它并没有改变我们正在创建一个对象、“捕获”构造函数中的某些状态并使用重载operator()进行比较的基本事实。
当然,比较恰好是我们进行排序之类的事情所需要的。这是lambda 表达式和函数对象的常见用法,但我们当然不限于此。再举一个例子,让我们考虑“规范化”双精度集合。我们想要找到最大的一个,然后将集合中的每个值除以该值,因此每个项目都在 0.0 到 1.0 的范围内,但所有项目之间都保留与之前相同的比率:
double largest = * std::max_element(begin, end);
std::for_each(begin, end, [largest](double d) { return d/largest; });
Run Code Online (Sandbox Code Playgroud)
这里我们再次有几乎相同的模式:创建一个存储一些相关状态的函数对象,然后重复应用该函数对象operator()来完成实际工作。
| 归档时间: |
|
| 查看次数: |
1432 次 |
| 最近记录: |