青山镇

青山镇不大,镇上只有一家铁匠铺。铺子的主人叫老铁,打了四十年铁,手艺方圆百里闻名。

老铁的工具多得数不清——锤子三十六把,钳子二十四把,錾子、冲子、锉刀不计其数。镇上的人都来找老铁借工具:木匠借锤子,瓦匠借撬棍,连教书先生做教具也要来借一把小钢锯。

老铁人好,谁来借都给。但他有个毛病——从不记账。


麻烦来了

上个月,木匠老张来还锤子,却发现锤子少了两把。问遍镇上的人,谁都摇头说"不是我借的"。

瓦匠老李借了一把撬棍,三个月没还。老铁去问,老李一拍脑袋:"哎呀,忘在工地上了!"工地早拆干净了,撬棍找不到了。

更糟的是,剃头匠老王来借剪刀。老铁说:"剪刀?我记得还有三把的。"翻遍铺子,一把都没有。后来才知道——一把在张木匠家压箱底,一把被李瓦匠拿去撬钉子撬断了刃(也没还回来),还有一把三个月前借给了镇上来的货郎,货郎早走了。

老铁坐在铺子里叹气:"工具倒是都打得好好的,可到底在谁手里,我全不知道。"


工具册

老铁的儿子小铁从省城回来,看见父亲发愁,笑了。

"爹,我在城里的工厂干过。他们有几百号工人、上千件工具,从来没丢过一件。因为他们有一本——工具册。"

小铁拿出一本空账本,在第一页画了三栏:

工具 借用人 借用方式
大铁锤 张木匠 独占
钢锯 李瓦匠 独占
撬棍 教书先生 借用,不占

"爹,你看。"小铁指着这三栏,"每一件工具,我们都记清楚三件事:工具本身、谁拿着它、怎么个拿法。"

"第一栏 '工具'——就是这个工具叫什么,放在哪。对应的就是内存地址——new 出来的那块内存,总得有个名字。"

"第二栏 '借用人'——就是谁拿着这个工具。这个最重要。 工具弄丢了,第一个要问的人就是借用人。在程序里,这就叫 '所有权'——谁负责最后把工具还回来,谁就是所有者。"

"第三栏 '借用方式'——分三种。"

小铁在第三栏旁边画了三个圈:


第一种:独占。

"一把锤子只能一个人用。张木匠拿走了大铁锤,李瓦匠就不能同时拿。张木匠用完,他在工具册上把自己的名字划掉——锤子自动回到铺子里。他如果忘了划?工具册上他名字还在,谁都知道锤子在张木匠手里。"

这就是 unique_ptr。一把锤子,一个主人,主人走了锤子自动归还。


第二种:共用。

"一把大钢锯,有时候张木匠要用,有时候李瓦匠要用,有时候教书先生也要用。工具册上写三个人的名字,每个人用完就在自己名字后面打个勾。三个人都打了勾,钢锯才回铺子。"

这就是 shared_ptr。一个对象,多个持有者,最后一个持有者离开时释放。关键是:你知道有哪些人在共用,谁也不会忘了还。


第三种:借用,不占。

"教书先生明年来镇上教书,现在先来看看工具。小铁把撬棍递给他看看——但是工具册上不写他的名字。因为教书先生只是 '看看',不是 '拿走'。他不能把撬棍带回家,只是在铺子里用一下。工具归铺子管,教书先生不负责还。"

这就是裸指针和引用。你拿到了对象的地址,但你不是它的所有者。你只是借来看一眼,别把它带走,别对它负责。 对象的生死,由真正的所有者(那个在工具册第一栏签了字的人)决定。


不会丢工具的铺子

自从有了工具册,青山镇铁匠铺再也没有丢过一件工具。

每天早上,小铁翻开工具册,第一栏里列着所有工具,第二栏里记着谁在用,第三栏里标着怎么用。下午收工,小铁再翻一遍——独占的工具,借用人走了名字就自动划掉;共用的工具,最后一个用完的人自动把它送回铺子;借来看看的,看完就放回去,不在册子上留痕。

有一次,货郎又来了,想借一把剪刀。小铁翻开工具册看了一眼:"不好意思,三把剪刀都在用——张木匠独占一把,李瓦匠和教书先生共有一把,还有一把剃头匠老王只是借看,但也得等他用完。"

货郎说:"我就拿去看一眼,保证还。"

小铁摇头:"拿去看一眼'在工具册上也得记。不然你走了,剪刀找谁要?"

货郎懂了,在工具册上签了名。


老铁悟了

一个月后,老铁在铺子里喝着小酒,跟小铁说:"我以前以为,铁匠铺最重要的是工具——锤子要重,钳子要紧,錾子要利。"

"现在我知道了。铁匠铺最重要的,是知道每一把工具在谁手里。"

小铁笑了:"爹,这就是城里程序员说的——"

"别跟我提那什么指针不指针的。"老铁一摆手,"我听不懂。但我知道——工具可以借出去,但必须知道找谁要回来。"


后记

C++ 的内存管理,说到底就是老铁的工具册:

  • new = 打了一把新工具
  • unique_ptr = 独占借用,借用人走了,工具自动归还
  • shared_ptr = 共用借用,最后一个用完的人负责归还
  • 裸指针 / 引用 = 借用不占,只是看一眼,别带走
  • 内存泄漏 = 工具借出去了,册子上没记,找不回来了
  • use-after-free = 工具已经还回铺子了,你还当自己拿着,伸手去摸——空的

老铁以前丢工具,不是因为他不会打铁,是因为他没有记账。程序员写 new 忘了 delete,不是因为他不会写代码,是因为他没有想清楚——谁拥有这个对象。

铁匠铺最重要的不是工具,是工具册。代码里最重要的不是内存,是所有权。