特别节目·中文精读

本贾尼·斯特劳斯特卢普:C++

Bjarne Stroustrup: C++

C++ 设计者谈语言的设计取舍、零开销抽象的理念,以及编程教育与好代码的标准。

官方音频00:00 / --:--
中文精读由 AI 基于原始访谈改写,非官方翻译,可能存在偏差或错误;表述以原音频与官方文稿为准。说明

你踩下刹车踏板的时候,踏板和刹车片之间没有机械连杆。中间隔着一台计算机。

这台计算机上跑的代码,很多是用一门 1979 年开始写的语言实现的。C++。

YouTube 的大量关键后端组件是 C++ 写的。Google、Facebook、亚马逊、推特、微软的多数应用、多数数据库系统,还有那些在物理世界里真正干活的东西:汽车、机器人、发射进太空的火箭。这些东西的共同点是,出错不能靠"重启一下"解决。

2019 年,Lex Fridman 把这门语言的作者本贾尼·斯特劳斯特卢普(Bjarne Stroustrup)请到播客里聊了一个多小时。这场对话没停在语法特性上,它更像一个人在解释自己四十年的取舍:什么必须快,什么必须可靠,什么宁可不要。

第一门真正爱上的语言,和那个算星形的下午

斯特劳斯特卢普写的第一个程序,是大学二年级的事。任务是算出一段曲线的形状,然后把周长上的点连起来,生成星形图案。输出设备是一台纸带打印机。

他学编程算晚的,第二年才正式开始。第一门让他产生感情的语言是 Algol 60。之后的路走得不太顺:Fortran 没让他心动,Pascal 也没有。

Pascal 总是在挡我的路。(斯特劳斯特卢普)

他转向汇编,觉得那反而更有意思,接着写了大量 BCPL。BCPL 时期是他第一次靠编程赚到钱,硕士学费就是这么付掉的。

真正打开新世界的是 Simula。

Simula 是 Algol 60 的扩展,本来为仿真而做。做这个语言的人顺手发明了面向对象编程、继承、运行时多态。斯特劳斯特卢普在这里第一次意识到一件事:程序的复杂度可以随代码规模线性增长,而不必随规模平方增长。

你完全可以做到很好的模块化。这对我来说是个惊喜。

另一个惊喜关于类型系统。Pascal 的类型系统处处跟他作对,Simula 的类型系统比 Pascal 更严格,却处处帮他的忙。这个反差后来成了 C++ 的一条底层信念:类型系统要强,同时必须可扩展、可变形。

那个年代的 Pascal,带着一种道德腔调

他解释这个差别的时候说得很直接。Pascal 是 Niklaus Wirth 能为当时的需求设计出的最简语言。它有一种高度说教的语气:如果你能用 Pascal 表达,那就是好的;表达不出来,那就是不好的。

Simula 反过来,鼓励你自己建类型系统。你不必把自己塞进 Wirth 划定的那个世界里,你可以为自己手头的问题现造一套类型。

这已经很接近今天说的领域特定语言(DSL)。你真正在建的,是一组类型以及类型之间的关系,这组关系让你能用贴合应用的方式描述问题。

顺带一个冷知识:所有用 class 这个词表示类型的语言,都是 Simula 的直系或旁系后代。Simula 的设计者是一群数学家,他们不按"类型"思考,想的是集合和元素类,于是把类型叫成了 class。

消防车转弯,为什么需要继承

很多人对继承的理解停在教科书上那个"动物→狗→猫"的例子,从来没搞明白它在解决什么问题。斯特劳斯特卢普给了一个他亲眼见过的例子。

你有一个车辆集合:自行车、轿车、消防车、坦克。现在你让它们左转。如果你不知道它们是什么,你得写一大堆判断:如果是自行车,按自行车的方式转;如果是轿车,按轿车的方式转;如果是消防车,按消防车的方式把方向盘打死。

代码会变成一坨巨大的 case 和 if 嵌套。

有了继承,你只需要对着基类说一句"转"。每个具体类型自己知道该怎么转。

60 年代有人真拿它去模拟和优化某个地方的紧急救援调度。(斯特劳斯特卢普)

这个例子里藏着一个很硬的需求:你不可能每来一种新车型就重写一遍调度代码。继承和运行时多态之所以值得存在,是因为真实系统会不断长出新东西,而调度逻辑必须稳如磐石。

为什么 Lisp 不是他的菜

访谈里 Fridman 提到 Lisp 是自己最喜欢的语言之一,问斯特劳斯特卢普怎么看。回答很诚实:Lisp 当然重要,当然有意思,很多人在里面写代码,然后在要上生产环境的时候,把它重写进 C 和 C++。

他的领域有一组绕不过去的约束:性能、可靠性、可部署性、硬件成本。

我不喜欢东西太动态。写一段既完美又灵活的代码,能塞进一台小计算机,还能装进波哥大的电话交换机里,这个概率有多大?(斯特劳斯特卢普)

他举了一个具体场景:某个周日的深夜,波哥大的电话交换机崩了,你发现自己进了调试器。周围有个程序员吗?

概率是零。

他很清楚自己服务的只是少数派。全球七八成的代码不在这种约束下,写 Web 前端和数据分析脚本的人完全有资格享受动态语言带来的速度。但总得有人负责从那些高级而灵活的语言,一路下到硬件。他选了这个位置。

在这个位置上,一门语言的价值不体现在写起来多舒服,而体现在写出来的东西十年、二十年、三十年之后还能在苛刻条件下跑。

五门语言,第二门最重要

他说一个专业程序员应该懂至少五门语言。理由跟会不会用它们没关系,学语言这件事本身就是训练。

只会一种语言的人,就像一个只待在自己文化里的人,很容易以为自己的活法就是唯一的活法。(斯特劳斯特卢普)

他估计自己懂二十五门左右。他也承认那个年代学语言容易,语言本身小,不用连整套编程环境一起啃。

他随手挑了五门:机器码、C++、一门函数式语言(Haskell 或 ML 都行)、一门能快速出活的语言(JavaScript、Python、Ruby 随便挑一门)。

关于机器码,他给了一个今天写高层代码的人必须记住的判断。手写底层代码换性能这件事已经过去了,C++ 优化器比人聪明。但如果你不懂机器码和机器架构,你根本欣赏不了优化器在做什么。

更实际的一点:你把缓存弄乱了,性能直接掉一百倍,这个惩罚跟用什么语言无关。

他说,现在他最喜欢的优化方式,是把代码里那些"聪明的部分"全删掉,看看还跑不跑得快。有时候删完更快。

所以他需要的其实是抽象机制,让他能把代码写得紧凑、写得让优化器看得懂。

Commodore 64 上的 Pong,和一条不断缩短的代码

他提到 Jason Turner 在 CppCon 上的一场演讲。那个人决定在一台 Commodore 64 上写 Pong。这台机器的特点和今天的微控制器很像:有专用硬件,内存小得可怜,速度慢。

现场直播的过程是这样的:他先用相当直白、相当低层的方式写,然后用 Compiler Explorer 实时看到 C++ 编译成汇编。接着他开始提高抽象层次,用 C++11 的特性,代码变得更干净、更好维护、更短。

它一直在变短。

斯特劳斯特卢普的结论是,他不可能在任何合理的时间内手写出编译器从现代 C++ 生成的那种质量的机器码。他甚至说,那段看起来像 C 的代码,变成机器码之后反而更丑、更大。

这个演示的价值在于它把"抽象"从一个审美偏好拉回到了工程事实:能被优化的抽象才有资格存在。

刹车上没有机械连杆

回到开头那个画面。汽车刹车踏板和刹车系统之间已经不是直接的机械连接,中间是计算机。这台计算机最好别出错。

现代的汽车要用几百万行代码,自动驾驶和车道保持这类系统更甚。Fridman 提到他跟监管者聊过,那些人对软件安全性极度紧张,因为这些代码最终会做出可能致死的决策。

斯特劳斯特卢普的回答绕开了一个常见的思维陷阱。

安全性、性能、保密性,都是系统属性。人们习惯一次只看系统的一个部分,然后说"这块是安全的"。你如果真想要可靠、想要性能、想要安全,你必须看整个系统。(斯特劳斯特卢普)

他甚至反过来讲了一层:把单个零件打磨到完美,可能并不是获得最高可靠性的最佳路径。

C++ 没有类型安全,你可以把它弄崩。他的回应很干脆:任何跑在计算机上的东西我都能弄崩。我要真想进你的机器,我大概会去试 SQL 注入,而不是去破你的类型系统。

识别丑代码比识别美代码容易

怎么让代码变可靠?他的答案只有一个词:简化。

不是先上测试框架,也不是先上运行时检查。第一步是让代码变少,让出错的概率变低。他引了爱因斯坦那句老话:尽可能简单,但不能更简单。

具体怎么做?他给了一个判断标准。如果你思考问题的方式没有体现在代码里,那代码里就缺了一块,那一块只存在于你脑子里。看代码的人能看到它做什么,看不到你想什么。

当你能把想法直接表达出来,代码就变得可维护、易找错、易修改,也更好测试。顺便,它跑得更快。于是你需要更少的机器,可能出故障的硬件也就更少。

至于怎么判断一份代码是不是干净的,Fridman 问得很直接:假设把福特 F-150 的代码给你,你能看出来这是不是一团乱麻?

答案是有经验的开发者能闻出来。

识别丑比识别美容易。原因是美有时候来自创新和不寻常,你得想一会儿才能领会。而混乱有很多共同点。(斯特劳斯特卢普)

他这些年一直在把"好代码长什么样"写成规则,目前这一版叫 C++ 核心准则(C++ Core Guidelines)。他的表述很克制:规则本身造就不了好程序员,但它限制了损害的范围,减少了犯错的可能性。

他用了一个很漂亮的类比。把所有雕刻的规则写进大理石,也变不出米开朗基罗。写一个好程序,除了手艺,还需要一点灵感、理解和天赋,而灵感没法监管。你能规定的只有手艺那一层。

静态分析真正的作用是教育

他很看好静态分析,理由在于它能抓"用法",抓 bug 只是附带。

他举的例子是资源泄漏。在 C 那一层,你有一个 malloc,就必须有一个配对的 free。如果两个都在同一个函数里,分析起来很容易,看到一个 malloc,就该在附近找到一个 free。但如果中间隔着一堆代码,指针被传出函数,free 在别的地方发生,问题立刻变成几乎不可解。

静态分析跑一遍程序,会找到一些泄漏,也会找到大量它分析不下去的地方,因为结果依赖运行时,或者依赖分析器够不够聪明,或者干脆跑不完。

一旦你把静态分析和一套使用规则结合起来,你就能看出规则为什么被违反。你不需要解决全部问题,你需要的是别掉进那个无底洞。

他对静态分析的评价很高:它像一个教学工具,在你写未来代码的时候给你指路。

更深一层的原因是他对错误处理代码的悲观。代码一旦跑起来才报错,你就必须写错误处理器。错误处理代码是最难写的代码之一,因为出问题的时候,你往往并不确切知道出了什么问题。你不知道问题是什么,你凭什么恢复?

所以他宁愿在代码跑起来之前就把错误找出来。

抽象不该比手写慢

这里到了整场对话的技术核心,零开销原则。

如果你有一个抽象,它相对于等价的低层代码,不应该带来任何额外成本。(斯特劳斯特卢普)

举矩阵乘法。用矩阵这个抽象写出来的乘法,应该快到让你没法通过退回数组和指针再快一点。

实际上有人写出来了,而且比 Fortran 快。原因很反直觉:一旦你有了正确的抽象,编译器就能消除临时对象,做循环融合这类优化。这些事手工做极其痛苦,在低层语言里几乎做不好。

这个故事有个流传很广的后续。八十年代,他的朋友 Doug McIlroy 在演讲后问了一句:如果我把你这段 C++ 改写成 C,会快多少?答案是会慢很多。

他特别澄清了一点:他从来没说过 C++ 是面向对象编程语言。这句话被引用了无数次,但原话不长这样。

我说的是 C++ 支持面向对象编程,也支持其他技术。(斯特劳斯特卢普)

这个区别有实际后果。复杂而有趣的问题,最好的解法通常要同时用到面向对象、数据抽象、函数式,以及传统 C 风格代码。一个只允许一种范式的语言,会在某个环节逼你绕路。

他在设计 C++ 的时候很早就明白一件事:不能只是往语言里加特性。看到什么好看就加,听谁要什么就加,一个个加进去,最后得不到一个协调的整体。你需要一组原则来决定什么该进、什么不该进、一个特性要改到什么程度才能进。

这些原则里大部分不是语言技术问题。比如"不要破坏静态类型系统",理由很朴素:他要东西在合理的硬件上可靠地跑。

编译器的性能从哪里来

零开销原则靠什么实现?三样东西同时到位:编程技术、语言特性、实现技术。

他提到 Brian Kernighan 说过的一句话,C++ 能成功,部分原因是他早期编译器里下的功夫。他自己写了不少用这套东西的代码。

一个人不可能同时精通所有领域。他举了并发编程的例子:你需要懂并发算法,需要懂无锁编程的细节,需要懂编译器技术,还要懂应用领域,比如某些图形学或者 Web 服务器那类负载。这些东西塞不进一个脑袋,但一个两三个人的小团队可以做到。

四种编译器,和他讨厌的那个词

为什么 C++ 有很多种实现?没人做过这个决策,它从一开始就是既成事实。

他设计 C++ 的时候,世界不是一台 x86 加 Linux。他想的是要利用各种已有的后端基础设施。然后他发现用户在用二十五种不同的链接器。他可以写一个自己的链接器,但他不可能写二十五个,同时还推进语言本身。

所以多种编译器是从一开始就存在的前提。今天至少有四个前端:Clang、GCC、微软、EDG。EDG 供应了大量独立组织和嵌入式行业。后端则多得没法数,嵌入式世界的芯片架构差得太远,单一实现从来就不是选项。

他顺手说了一句:他讨厌单一文化。

单一文化的危险在于,拥有它的人会僵化,没有竞争,没有创新动力,反而有大量动力去给变化设障碍,因为这个世界对他们太舒服了。GCC 曾经很好,后来有点僵,Clang 出现之后微软也变好了很多。少数几个前端的存在,给标准符合性、性能、错误信息质量、编译速度都施加了压力。

构造函数和析构函数

问到他个人最喜欢的 C++ 特性,答案很基础:构造函数和析构函数。

构造函数为一个对象建立好使用环境,析构函数在结束时清理掉所有烂摊子。这是 C++ 的关键,是它不必依赖垃圾回收的原因,是它能给出可预测性能的原因。

这个想法有个更广为人知的名字:RAII,资源获取即初始化。他的自评是,这是他为什么不该去做广告的例子。这么好的一个想法,被他起了这么个名字。

时间点也很早。1979 年,他开始做那个当时叫"带类的 C"的东西,大概第二周,构造和析构就进去了。

逻辑是一步步推出来的。你控制了对象的出生和死亡,就必须控制拷贝,因为拷贝是另一种创建对象的方式。控制完拷贝,你还得能把对象搬来搬去,于是有了移动语义。这组操作定义了一个 C++ 类型能用什么方式存在。

Fridman 把这件事抬到了哲学层面:创建一个类型的实例、释放它的资源,就像出生和死亡定义了一个人的生命。

斯特劳斯特卢普接了这句话,并顺势说,哲学很重要。没有哲学就做不好语言设计,因为你决定的其实是人们能表达什么、怎么表达。

概念为什么拖了三十年

模板是 C++ 泛型编程的基础。编译器看到模板和参数,把两者结合,生成一段代码,效果等同于你专门为那个类型手写了一份。信息拿得更全,所以有时生成的代码比人写的还快。

代价是调试。优化后的代码从来就不好读。1983 年那个 C++ 前端就已经做了前端和后端两层优化,生成的 C 代码是给机器看的,不是给人看的。加上模板之后更糟,因为优化器会把代码不同部分的信息拖到一起。

实际做法就是关掉优化器调试,先把逻辑层面搞对。

真正难的部分在概念(concepts)。

1987 年他做模板的时候,想要三样东西:极度灵活,能表达他想象不到的东西;和手写代码一样快或更快;能表达模板对参数的要求,也就是接口约束。

前两个他做到了,第三个困扰了他二十多年。函数参数的类型检查是他推动进入 C 的,他很清楚好接口的价值。但模板的接口约束,他和当时所有人都不知道怎么同时满足这三点。

我必须有这个工具,否则它就不算 C++。(斯特劳斯特卢普)

2003 年前后,Gabriel Dos Reis 开始系统分析这个问题,印地安那大学的一个老朋友也启动了项目。他们以为两三年能搞定,那样 C++11 就会在 2006 或 2007 年发布。

结果核心思路就是错的。

我们太因循了。它不太贴合 C++。它对隐式转换的支持不好,对混合类型的算术运算支持不好。很多东西来自函数式社区,而那个社区处理多类型的方式跟 C++ 不一样,对能表达什么有更多限制,也没有那么严苛的性能要求。(斯特劳斯特卢普)

他们试得很苦,也取得了一些成果,但最后编译不够快、用起来太难、运行不够快,除非优化器达到当时还不存在的水平,而那个水平今天也还没到。

最后落地的方案是另一条路:一组参数定义一组操作,通过一张交互表来查找,思路类似虚函数表,然后再想办法优化掉这层间接。这个概念在 C++20 里,2019 年已经在 GCC 和 Clang 里可用好几年,微软即将跟上,标准库也已经在用。

概念本质上就是编译期谓词。你能问一个类型:你是序列吗?你有 begin 和 end 吗?你是随机访问序列吗?你有下标吗?你的元素类型支持小于号吗?

以前模板说的是"我接受任何类型",现在它能说"我接受满足这些条件的类型"。

这个词是 Alex Stepanov 起的,C++ 泛型编程之父。他的理由很直白:这些都是一个领域里的基本概念,就该叫概念。

一年开三次会,一个组织一票

标准化的起源很有意思。八十年代初,IBM 和 HP 各来了一个人,坐进他办公室,说要标准化 C++。

他觉得莫名其妙:这语言还没做完,没到能正式标准化的程度。

对方的回答是:你不明白。我们的组织依赖 C++,我们不能依赖一个属于别家公司的东西,那家公司可能是我们的竞争对手。我们当然可以信任你,但你可能被公交车撞了。

后面还有半句更关键的:你最好参与进来,否则我们就自己搞,而我们知道你做得更好。

连哄带劝,1989 年底在华盛顿开了第一次会,二十五个人的规模,据说创了这类会议的记录。会上先给大家讲 ANCI 的规则。

这套流程完全开放:交一笔会费就能参加,当年不到一千美元,现在大约一百八十美元。一年开三次会,每次一周,技术讨论,然后投票。投票是一个组织一票,所以 IBM 带十个人来也压不住场面。

他对比了一句:这比让他一个人决定要好,也比让 AT&T 这样的单一组织决定要好。标准化很不愉快,很像民主。而民主,按丘吉尔的说法,是最坏的制度,除了其他所有制度以外。

1990 年第一次技术会议,1998 年发布第一个标准。当时他们想,这次学会了,下一个六年就能出来。

结果花了十三年。

原因是第二系统效应:既然已经知道怎么做了,那就做得更好一点吧,于是野心膨胀,时间拉长。还有一个规律,越是临近发布,越有人冒出一个绝妙的主意,非加不可。

C++11 那次他们赌对了,加进去的东西确实救了 C++。之后有人想复制这个成功,但没那么戏剧性,而且越拖越久。

解决办法很简单:发得更勤。十年周期里出现一次延期,你意识到的时候已经过去十一年了。三年周期里出现延期,三四年就能补回来。于是 14 准时,17 准时,20 也会准时。

这个可预测性对编译器实现者极其重要,因为他们有了明确目标,并且真的交付。他的原话是:C++11 之后,多数编译器花了两年才做到"够用";14 的时候情况好了很多;17 的时候几乎所有厂商都在当年发货;而 20,因为大多数实现在 2019 年就已经提前交付,他有底气说这次也会准时。

语言设计是哲学问题

Fridman 问他,有没有可能未来出现一套统一的语言设计原则,就像物理学追求统一理论那样。

斯特劳斯特卢普把两边的年龄摆了出来。现代物理从伽利略算起,七百年。现代计算从 1949 年前后算起,七十年。差了十倍。而且物理学不用操心"用户"这件事,语言设计得操心,因为写程序的是人。

所以他觉得这个领域非常不成熟。但在哲学层面,确实存在可以达成共识的原则:别泄漏资源,别在运行时处理你本不必处理的错误,别破坏你自己定下的类型系统。类型系统可以有很多种,但一旦选定了一种,就不要打破它。

他也看到了实际的收敛。C++ 早年采纳的 RAII,二十年后在垃圾回收语言里以 disposal pattern 之类的形式出现,那些语言也在努力不要事事依赖 GC。

他讲了一段很有意思的往事。有一次他和 Common Lisp 的几位设计者坐在一起聊语言和语言特性。一开始什么都没谈成,彼此看不顺眼,他甚至开玩笑说自己不喜欢那些括号。聊着聊着突然通了:他描述自己遇到的某个问题,以及他是按什么原则解决的;对方说他们遇到过另一个问题,也是按同样的原则解决的。他们一起走过了 C++ 的大块内容和 Common Lisp 的大块内容,发现两边的推理方式、底层原则其实高度相似。

不同的是约束条件和目标用户。

原则可以共享,取舍不能。这是他对"语言统一"这个问题最诚实的回答。

84% 的准确率,和那剩下的部分

Fridman 把话题引向机器学习。他的类比是:写程序是造一个接受输入、产生输出的东西;机器学习是在数据上训练出这么一个东西。区别在于后者是模糊的、有噪声的,像人一样。

斯特劳斯特卢普的回答毫不含糊:那是另一个世界。

我不喜欢模糊。人们说希望人人都能编程。我不想人人都来编我的飞机控制器和汽车控制系统。我希望那是工程师做的,是专门受过教育、专门被训练来做这件事的人做的。(斯特劳斯特卢普)

他把 C++ 的定位说得同样直白:这门语言服务的是专业人士,是一把锋利有效的工具,交给那些追求精确的人。

你不会让不懂数学的人去做计算。掰手指头是送不上月球的。

然后他给出了一个具体的数字判断。Fridman 提到 99.8% 的准确率,他的回应是:他看到的更多是 84%,而且要靠大量血汗才能推到 92.5%。

84% 在你做预筛选、把结果交给人类复核的场景里完全够用,甚至比人做得好。但它在涉及生命的场景里不够。这个数字划出的是适用边界,跟技术本身好不好没有关系。

他对自动驾驶的担忧落在一个非常具体的地方。他听说过一种设计:当 AI 系统遇到处理不了的情况,就把控制权交还给人类。而那个人可能正在看书,可能正在睡觉,他被叫醒后有三十秒,或者只有三秒,要搞清楚 AI 没搞定什么,然后做对。

这很吓人。人和机器之间的交接成本,怎么算?(斯特劳斯特卢普)

他说完又补了一句:这个问题被摆出来了,还谈不上无解。他自己的偏好是干脆别依赖人。开核反应堆也好,开自动驾驶也好,最好是设计一个永远不需要向人求助的系统。

这句话从一个造了四十年可靠系统的人嘴里说出来,分量不一样。AI 可不可靠是另一回事。一个要求人随时待命的系统,等于把两种性质完全不同的不可靠叠在了一起。

他也承认这些系统就架在 C++ 之上。这也是他必须持续关注那个领域的原因,尽管他明确说自己永远不会成为那方面的专家。这又回到他反复强调的那件事:今天没有哪个大系统是用一种语言写的,而且理由充分。不同领域需要不同工具、不同原则,然后它们互相接触。

四十年里最让他骄傲的,是望远镜和火星车

Fridman 问他,回头看这辈子的工作,哪一刻让他觉得"我干得不错"。

答案没有落在任何一个语言特性上。C++ 是几个好想法、大量苦工、一点运气的组合。他有几次想离开,但总被拽回来。一半是因为他在这个领域最有效率,另一半是因为在 C++ 的上下文里做事,影响面大得多。

如果我明天做对了什么,有四五百万人会捡起来用。如果我去别的领域,我得先学,然后得把它建起来,然后还得看有没有人愿意用。(斯特劳斯特卢普)

真正让他撑下来的,是能看到人们拿它做出了什么。

他去看过莫纳克亚山上的望远镜。他去看过福特怎么造车。他去过 JPL,看过火星车是怎么做出来的。他说了句很朴素的话:这么多酷东西在发生,而大部分酷东西是相当不错的人做出来的,有时候还在相当不错的地方,剑桥、索菲亚·安提波利斯、硅谷。

代码是中心,但不止是代码。

这场对话真正在讲什么

把它当成一场关于 C++ 的访谈很容易,但里面那些判断的适用范围远大于一门语言。

他反复回到同一个结构:先确定约束,再谈方案。他的约束是性能、可靠性、可部署性;他的结论是抽象必须免费,标准必须准时,错误必须在运行前抓住。所有具体的语言特性,都是从这个约束推导出来的结果,没有人先喜欢上某个特性再回头找理由。

他对简化有一段话值得单独记住。如果代码里没有体现你对问题的思考方式,那么你的思考就只存在于你脑子里,代码看不见它。而看不见的东西没法维护、没法测、没法改。他后面那句更妙:当你能直接表达想法时,代码顺便也跑得更快了。

还有一层常被忽略的判断:安全、性能、保密都是系统属性。单点做到完美,不等于系统可靠。这个视角对今天做 AI 系统的人尤其刺人,因为整个行业的叙事都集中在模型这一层。

最后是他对"什么不该做"的坦率。他讨厌单一文化。他承认 C++ 被用在比特币挖矿上,耗电量相当于瑞士,而且主要服务犯罪。他明确说不希望所有人都来编程。一个做了四十年工具的人,能清楚说出自己的工具不该用在哪,这种自觉比任何技术洞见都稀缺。

他在访谈最后被问到还有什么要说,只留了一句:会做得更好。

如果你想把这场对话变成行动

  • 学一门和你日常工作范式完全相反的语言。你已经会 C++,去写半年 Haskell 或 Lisp;你只写 Python,去写一段真正需要手动管理内存的代码。第二门语言带来的收益远大于第三门。
  • 下次性能不够时,先删代码,别先加优化。把那些"聪明的部分"拿掉,看是否反而更快。缓存和分支预测的惩罚比指令数更狠。
  • 给你的模板或泛型接口加上约束。如果你在写 C++20,用概念;如果在写别的语言,用类型标注或接口声明。让"我接受任何类型"变成"我接受满足这些条件的类型",报错信息会从一屏变成一行。
  • 把资源管理和对象生命周期绑在一起。构造函数获取,析构函数释放,不要写手动清理路径。这条原则在任何有 RAII 或类似机制的语言里都成立。
  • 在写第一行代码之前,先写下三条这个项目不可违反的原则,比如不破坏类型系统、不在运行时处理可预见的错误、不引入无法静态检查的隐式状态。原则要在特性之前定,否则你只是在堆功能。
  • 做安全关键系统时,别把"AI 处理不了就交给人"当兜底方案。先算清楚人要多久才能理解现场,如果这个时间不够,那这个设计就是错的。
  • 读一遍 C++ Core Guidelines。不一定用它写代码,用它训练自己闻出坏代码的能力。识别丑比识别美容易,而这项能力可以刻意练习。

本集资源与出处