2

闭包是什么, C++和Rust的视角

 3 years ago
source link: https://zhuanlan.zhihu.com/p/378270921
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

闭包是什么, C++和Rust的视角

微信公众号:CrackingOysters

闭包(closure),写过一段时间代码的人都会遇到。因为现在语言基本都有闭包这个概念的,可能叫法不一样,有的叫闭包,如JavaScript,Rust,有的叫lambda, 如Python,C++, Java。

v2-13dd4ab3b6a57e94ccc0971b39e5ac58_720w.jpg

闭包是什么

闭包就是——一个函数捕捉到了它创建时候的上下文(比如局部变量),当它被调用的时候,它使用到的那些上下文的变量仍然存在着。一个JavaScript例子更能直观的阐述什么是闭包

function NormalFunction() {
  let variableInContext = "I'm ";
  const closure = function (para) {
    console.log(variableInContext);
    console.log(para);
  };
  closure(3);
  variableInContext = "you are";
  closure(4);
  return closure;
}

const ret = NormalFunction();
ret(5);

打开浏览器的developer tool,敲入上面的代码,输出是

I'm 
3
you are
4
you are
5

根据输出,我们可以看到闭包closure在创建的时候,将它的上下文变量 variableInContext给捕获到了,然后我们在调用这个闭包closure(3), closure(4),的时候,这个variableInContext就会被闭包访问,这里是打印到控制台。最后这个闭包closure也被返回给NormalFunction的调用并存储在ret里面。而ret(5)就是再次调用这个闭包。

如果细心的读者会发现输出,一开始i'm,后面是you are,因为被捕获的上下文变量在中间被修改了。而JavaScript是引用为主的,所以我们捕获的变量和上下文的变量是同一个值。

在C++,闭包,也就是labmda的写法是

std::string variableInContext = "i'm";
auto closure = [&] (auto para) {
  std::cout<< variableInContext <<"\n";;
  std::cout<<para<<std::endl;
  };

一个包含更多内容的例子

这个图片来源于Lambda Expressions in C++。感兴趣可以阅读链接获取更多内容。看起来似乎信息量太多,难以记忆。实际上,你写过几回,就会知道和记得如何写了。因为它的结构比较简单,见下图

https://blog.feabhas.com/2014/03/demystifying-c-lambdas/

闭包的作用

我们为什么要使用闭包呢?不同的语言作用不完全一样,但一些共同的目的是

  1. 要写一个函数,但是太短了,懒得给一个名字给它(这也就是有时闭包叫匿名函数)。比如函数接受回调函数。
  2. 在当前上下文要做一些处理,但是还没到处理的时候,就先定义,等合适的时候再调用。比如scope guard(更多信息请看RAII:如何编写没有内存泄漏的代码 with C++)。
  3. (欢迎补充)

这些目的,能有其他替代方案吗?实际上你写一个有名字的函数,肯定是可以工作的,而且有些人更喜欢有名字的函数。只不过在这些场景下,闭包更顺手(在你学会以后)。

到这里,闭包的主要内容就结束了。剩下的多写写代码,顺便查一查就可以搞定了。

C++与Rust的闭包

各个语言都有闭包,但是C++和Rust的闭包还是有其特殊之处,这是由这两门语言要解决的特定问题和思考模型决定的。如果对Rust不熟悉,可以移步阅读Rust那些难理解的点(更新于5月17日)。如果也对C++不熟悉,那么下面可以跳过了,因为不是每个程序员都要熟悉每门编程语言。

C++/Rust的闭包的特殊在于

捕获分引用捕获和值捕获,引用捕获就是跟JavaScript的一样,它们指代同一个内容。值捕获就是用捕获的值初始化一个新的变量,但是具有同样的名字。比如下面的捕获对比

auto i = 4;
auto ClosureByValue = [=]() { std::cout<<i<<std::endl};
auto ClosureByReference = [&] { std::cout<<i<<std::endl;};
i = 5;
ClosureByValue();
ClosureByReference();

输出是4, 5。所以值捕获(ClosureByValue)是拷贝了一份i,而引用捕获(ClosureByReference)是同一个内容i。

而Rust还有一点特殊的地方是,闭包同样具有Fn, FnMut, FnOnce这三个trait。这源于Rust对于变量有三种约定:只可以读,可以修改,转移ownership。

v2-8ff38bbc9a09ddc686d49ac93a75c416_720w.jpghttps://zhauniarovich.com/post/2020/2020-12-closures-in-rust/

更多内容请看爬树贼溜的小松鼠:Rust那些难理解的点(更新于5月17日)


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK