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 Args&& ...args)接收。在lambda函数内部,这个参数包实际上和我们没关系,只是func要用,我们要将参数转发给func函数。转发的对象是一个个参数,而不是参数包,这是因为func接收的是参数,而不是参数包,只是我们为了不关心参数类型和数量而使用了参数包,因此在转发时,我们需要对参数包进行展开。

总结起来,我们的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;
        }
    );
}
如果觉得我的文章对你有用,请随意赞赏