1. 原型函数
高阶函数能对一个函数进行强化,拿一个js代码来举例子
function count(func) {
let count = 0;
return [
(...args) => {
func(...args);
count++;
},
() => {
console.log(count);
},
];
}
声明了一个count函数,接受一个func函数作为参数。
count函数内声明并初始化了一个计数变量count,通过闭包的方式塞给两个返回的函数。第一个返回函数在调用func函数后对count加一,第二个返回函数用于打印count现在的数值。于是count函数就给func函数增加了计数功能。
2. C++实现
将其改写为C++代码,第一个重点是如何处理这个count变量;第二个重点是count并不关心func接收几个参数、参数类型是什么、返回值是什么,怎么实现这个特性。
2.1 count变量
count变量在js代码中被丢进了两个闭包,其中第一个闭包对其进行写,第二个闭包对其进行读。所以count变量应当是以引用的方式传入了闭包。
局部变量不行:如果count变量在函数体内被声明为局部变量,显然count函数执行完毕后count变量就不存在了。
全局变量和局部静态变量不行:显然这个count变量应当在每次调用count函数的时候新创建一个,也就是说在多次调用count函数,想实现对多个func计数的场景下,这个count变量不能被共享。
综上,栈区不可用,上堆。上堆即考虑有没有所有权语义。每次调用count函数的时候,创建的count变量应为该次调用所有。该次调用所生成的函数对象有2个,这2个函数对象共同持有count变量。很明显,count是个共享资源。
auto count = std::make_shared<int>(0);
2.2 不关心具体的func
不关心传进来的func参数类型,参数数量,返回值,也就是不关心具体的func是什么。这个特性对应的技术已经非常明显了,范型。
template<typename Func>
auto count(Func func)
这样我们就没有对func进行任何限制,它可以是函数指针,也可以是函数对象(std::function),它可以有任何参数类型、参数数量、返回值类型,这些东西都在编译期决定。
此外我们还要对这个func进行加工,返回一个函数。这个函数首先要能对func进行调用,然后是对count变量进行加一操作。在C++中我们能用的相关工具包括std::function,std::bind,lambda表达式。显然std::bind不适合用在这里,我们没有参数与函数绑定的需求。std::function更多的是包装一个函数指针,而不是去模拟闭包,它失去了闭包捕获外部变量的特性。最后最适合使用的就是lambda表达式。
我们返回的函数对象需要对count变量进行写,对func函数进行调用,所以捕获列表传入func和count(std::shared_ptr)的拷贝。参数列表则实际上是func的参数,由于我们不关心func参数类型、参数数量,所以参数列表就用参数包(template
总结起来,我们的lambda表达式需要:
- 捕获count和func变量
- 接收参数包
- 将参数包展开并转发给func
- 让count值加一
[func, count](auto&&... args) {
func(std::forward<decltype(args)>(args)...);
(*count)++;
}
2.3 完整代码
template<typename Func>
auto count(Func func) {
auto count = std::make_shared<int>(0);
return std::make_tuple(
[func, count](auto&&... args) {
func(std::forward<decltype(args)>(args)...);
(*count)++;
},
[count]() {
std::cout << *count << std::endl;
}
);
}
3 条评论
看的我热血沸腾啊www.jiwenlaw.com
jqkpzy64219GI-文章很不错,感谢作者!https://kan.xiaoxinbk.com/7848.html/
iazvge98063DO-文章很不错,感谢作者!https://kan.xiaoxinbk.com/54112.html/