[译] Rust标准库有些特殊,让我们改它

本篇是对 RustConf 2023中的The standard library is special. Let’s change that.这一视频的翻译与整理, 过程中为符合中文惯用表达有适当删改, 版权归原作者所有.


今天我将讨论Rust的标准库,更具体地说,是关于标准库有何特殊之处,以及为什么我们应该改变这一点。首先声明一下,任何团队的成员都没有看过这个演讲,一切都是我的观点,部分是基于观察,部分是理想化的。显然,理想化的部分不保证会发生。

那么,当我说标准库是特殊的时候,我是什么意思呢?更重要的是,为什么我应该关心呢?事实证明,标准库在很多方面都是特殊的。

首先,标准库在稳定版上使用了nightly特性。虽然大量这些特性计划被稳定化,但有些特性打算永远保持在nightly上。这些应该尽可能被移除。

标准库还能够绕过一致性。这也不是绝对必要的。目前core、alloc和std是三个独立的crate。如果它们被合并成一个单一的crate,绕过一致性的需求就会完全消除。

标准库另一种独特的方式是它有一个预导入(Prelude)。其他crate也有预导入,但它们不是真正的预导入,因为它们必须手动导入到每个模块中。

标准库最明显的显著特殊之处是它不包含在cargo中。虽然看似无关紧要,但它确实有实际影响。标准库没有任何公开暴露的特性标志,这些标志可以用于基于能力的库。除此之外,这还可以允许禁用文件系统或网络访问。

std也是为单个优化目标速度而构建的,而像嵌入式系统这样的用例可能希望合理地优化大小。另一个限制是std不能独立版本控制,也不能做出破坏性更改。有一种可能的未来,可以在不需要std 2.0的情况下做出破坏性更改,但我今天不会讨论这个。

最后,我们已经在使标准库变得不那么特殊了。我认为以一种有组织的方式, 而不是像多年来那样临时进行这项工作是有意义的。


好吧,所以我们想让标准库不那么特殊。当然,这肯定有限制,对吧?是的,但这引出了一个问题:什么是(对std库)必须特殊对待的内在因素?

首先是语言项(language items)。语言项是编译器出于各种原因需要了解的函数、trait、类型等。目前有130个这样的项。例如,Add trait是一个语言项,因为编译器需要了解它以支持加法运算符。其中许多是必要的,比如用于语法集成和Rust的运行时,即panic和分配。然而,语言项的数量一直在增加,部分原因是有些是不必要的。Result及其变体都是语言项。这样做的唯一原因是优化。它们直到2020年8月才成为语言项,当时添加它们是为了获得轻微的性能提升。那么,为什么不为所有像Result这样的枚举实现优化呢?这避免了对语言项的需求,并且普遍适用于整个生态系统。这将为所有crate带来性能优势,而不仅仅是标准库。


另一个必须特殊对待的项是编译器内部函数(compiler intrinsics)。顾名思义,内部函数是由编译器本身实现的函数。有232个这样的函数,每个后端都必须实现它们。有些存在是为了直接与硬件交互,其中许多仅仅用于原子操作。其他内部函数的存在是为了访问只有编译器才有的信息。类型有多大?这个问题不问编译器是不可能回答的。像语言项一样,许多编译器内部函数可以被移除。取浮点值的平方根是一个内部函数,但这可以重写为使用内联汇编。在64位x86架构上,它只是一条指令。我实际上已经验证了这是可能的,而且没有任何副作用。我轻易找到的其他例子有assume,它可以trivially用unreachable来实现,以及unlikely,它可以用likely来实现。


语言项编译器内部函数构成了必须予以特殊处理的内在因素的主干。出于这个原因,它们的数量应该缩减到最低限度,并移到它自己的crate中,这个crate将保持特殊。这将允许标准库的其余部分继续成为另一个(普通)crate的旅程得以继续。



在开始时,我提到这部分是基于观察的。这是因为让std变得不那么特殊并不是一个新想法。在这方面已经进行了相当多的工作,可以追溯到多年前。有一个工作组叫做"std aware Cargo",其目标是允许用户在本地构建标准库。这个的实验性实现确实存在于nightly上。存在一些bug,比如与代码覆盖率不兼容,但它在很大程度上是可用的。未来可以而且将会改进其人体工程学,因为有时可能会有点麻烦才能让你想要发生的事情发生。

在更具体的方面,还有diagnostic_on_unimplemented。这在标准库内部已经存在了相当长的时间,作为rust_c_on_unimplemented。当一个trait没有实现但预期会实现时(可能是由于trait约束),它改善了错误消息。将其从rust_c属性转移到diagnostic命名空间已经被RFC接受,目前部分实现了。

也许列表中最令人惊讶的是deprecated属性,它最初只用于标准库。它有一段很长的历史,始于Rust 1.0发布之前。原始实现被重命名为rust_c_deprecated,并只提供给标准库。引入了一个新的deprecated属性,它没有任何代码与rust_c_deprecated相同。

deprecated属性在Rust 1.0发布后约一年就稳定了,但rust_c_deprecated仍然存在。这种情况一直持续到2022年3月,当时我完全合并了这两个属性的前端,此时功能几乎相同。我说几乎是因为仍然有一个区别,那就是对已弃用项的建议。这允许作者指出一个已弃用的项被什么替换了。目前,这必须通过note字段完成,这需要最终用户阅读并手动去进行更改。在这个例子中,alpha字段已被弃用,转而使用beta。这通过一个新的suggestion字段表示,并提供建议。编译器将产生更好的诊断信息。它将清楚地显示对Alpha的调用必须替换为beta。顺便说一下,这个建议是机器可应用的,所以它可以与cargo fixrust analyzer一起使用。这个功能自2019年1月就已实现,当时它是rust_c_deprecated属性的一部分。当它与deprecated合并时,该功能在nightly上对所有人开放,这就是它目前的状态。

标准库独特的一个有趣之处是其依赖于未指定的行为。在标准库中有一条评论说"只有标准库可以做出这个保证"。在标准库中可以找到一些类似的评论。当我第一次遇到这样的评论时,我觉得很奇怪。为什么标准库要这样做呢?

在我为这次演讲做研究时,我发现了两种主要情况,在这些情况下,标准库依赖于对其他库不保证的行为。一种涉及Niche value optimization (中文一般称为利基值优化),这种情况不是未指定的行为,而是根本不保证能编译。另一种情况是标准库依赖于胖指针的大小和布局,这就是这条评论所指的。

译者注:

Niche value optimization(利基值优化)是Rust编译器使用的一种内存优化技术。这种优化利用了某些类型的"空隙"或"利基"来存储额外的信息,从而减少内存使用并可能提高性能。让我为您详细解释一下:

  1. 基本概念:
    在Rust中,某些类型可能有一些值永远不会被使用。这些未使用的值就构成了一个"利基"(niche)。

  2. 常见例子:
    最常见的例子是Option<&T>。在正常情况下,&T是一个非空指针,因此Option<&T>理论上需要一个额外的布尔值来表示Some或None。但是,由于&T永远不会是null,编译器可以使用null指针来表示None,从而节省了额外存储布尔值的空间。

  3. 优化过程:
    编译器识别出类型中的未使用值,并利用这些值来编码额外的信息,通常是用于表示枚举变体或Option的None值。

  4. 应用场景:
    除了Option<&T>,这种优化也适用于其他情况,如包含不可能值的整数类型(例如,用作数组索引的非负整数)。

  5. 优势:

    • 减少内存使用
    • 可能提高性能(因为减少了内存访问)
    • 使某些类型更适合在FFI(外部函数接口)中使用
  6. 限制:
    这种优化是由编译器自动完成的,开发者通常不需要(也不应该)手动干预这个过程。

如果您想了解更多细节或有具体的使用场景需要讨论,我很乐意为您进一步解释。

只有一个问题:两者都是未指定的。编译器对这段代码没有任何保证。如果指针的布局发生变化,行为会悄悄地改变。更糟糕的是,如果指针的大小发生变化,那将导致未定义行为,因为某些代码将执行越界读取。

虽然胖指针的大小一直是usize类型大小的两倍,但据我所知,这并没有被正式保证。布局也是如此,它从未改变,但并没有得到保证。这段代码之所以能存在,只是因为编译器与标准库耦合。就标准库而言,它的代码在当前行为下工作,这就足够了。话虽如此,要让标准库变得不那么特殊,有两个选择。一是将胖指针变成一个语言项,这将确保它由编译器实现,从而保持同步。另一个选择当然是简单地保证胖指针的大小和布局。在我看来,这是更可取的,因为它也将是朝着稳定ABI迈出的一小步但却是重要的一步。


接下来讨论一些仍在nightly上的东西,但与大多数nightly特性不同,它在标准库的公共API中暴露出来。这个特性是负面实现(negative implementations)。负面实现在功能上是一个承诺,永远不会实现某个trait。可能最常见的用例是想要选择退出自动trait,即Send和Sync。

这在技术上是可能的,但这样做相当不符合人体工程学,因为它需要一些hack。这怎么可能呢?嗯,标准库包含有这些trait的负面实现的类型。具体来说,MutexGuard实现了!Send, Cell实现了!Sync,可变指针实现了!Send和!Sync。这很好,除了你可能想避免在内存中存储这些类型。相反,你可以将它们包装在PhantomData中,PhantomData在运行时不存在,但如果任何人都可以不使用这个hack就退出自动trait,那肯定会更简单。

负面实现还有其他用途。它们对trait解析也很有用,因为它们允许看似重叠的实现。

例如,标准库有一个实现,允许任何错误类型转换为Box<dyn Error>。但如果我们也想为Box<dyn Error>实现From<String>呢?为此,我们需要一个图表。在顶部是可能实现Error的类型,无论是当前还是将来。左边是已经实现的类型,右边是永远不会实现的类型。所有类型都从图表的顶部开始,但作者可以选择向下移动到左边或右边。对所有Error类型的blanket实现考虑了所有可能实现Error的类型,无论它们当前是否实现。出于这个原因,第二行是被禁止的。String可能在将来实现Error。为了满足编译器中的重叠检查,我们必须明确承诺String永远不会实现Error。这样做,我们将String移到了图表的右侧,将它们从blanket实现中排除。

值得注意的是,所有作者都可以选择在图表中向下移动。然而,向上移动是一个破坏性的变化,因为它是删除了对编译器和其他用户做出的承诺。这对正面和负面实现都是如此。


Rust的一个期待已久的特性是特化(specialization)。特化是一个特性,在某种程度上允许重叠的实现。这里的限制是一个实现必须是另一个的子集。然而,这仍然允许非常有用的行为。

目前,Default只为长度最多为32的类型实现。这是因为长度为零的数组不需要Default约束,而所有其他长度都需要。有了特化,我们可以为所有长度有一个默认实现,特化长度为零以避免约束。虽然这看起来足够简单,但这个例子目前在nightly上不能编译。

虽然特化是一个强大的特性,但要正确实现它也非常困难。当前的实现已知是不健全的。有一个min_specialization试图避免这种不健全,但这仍然不够。

可能是由于困难,特化的工作基本上停滞了。特化可能需要具有相当多类型理论知识的人来提出一个健全的子集,这需要大量努力才能稳定。尽管困难,但特化目前用于优化, 所有用法都经过仔细检查,没有出现在公共API中。

提议特化的RFC于2015年7月发布,就在Rust 1.0发布两个月后。在RFC中,特化被描述为"trait系统的一个相对较小的扩展"。我想我们都同意这有点乐观了。特化可能是本次演讲中提到的最困难的项目。

虽然特化可能非常困难,但这里有一个不应该那么困难的:Prelude是由crate提供的,在每个模块中自动导入的东西。这就是让你可以使用Vec却不必手动导入它的原因。

标准Prelude的内容默认是隐式的。Alloc Prelude过去存在,但它被删除了,因为它的内容不是自动进入作用域的。

所以我有一个问题:为什么不让每个crate声明一个Prelude呢?虽然一些crate确实有它们称为Prelude的模块,但它们的内容不是自动进入作用域的。我想做的事情从设计角度来看相对简单。

首先,我们可以注解任何将成为Prelude一部分的项。任意数量的项可以被注解,它们不必在一个共享模块中。在这个例子中,我们也重命名了该项。这与普通的use语句相同,它被用来避免潜在的命名冲突。

如果我们想在某个位置排除Prelude怎么办? 没问题,模块可以根据需要选择退出。这可以通过在模块上放置注解并指示我们想要排除哪些crate的Prelude来实现。

也许最重要的问题是,什么阻止crate声明巨大的Prelude,破坏每个人的体验?这有一个简单的解决方案:留给最终用户。最终用户选择在Cargo.toml中使用哪些Prelude。用户必须明确选择使用给定crate的Prelude。这允许最大的灵活性,因为如果没有明确请求,什么都不会进入作用域。

虽然自定义Prelude最初在2015年2月被提出,但这与那个提案有显著偏差。我相信crate Prelude在适度使用时会提供显著的好处,像itertools和rayon这样的crate是极好的用例。


今天的最后一项是稳定性属性。这可能是标准库能做而其他库不能做的事情中最明显的方式。稳定性属性用于指示一个项是否稳定。例如,OnceCell是稳定的,稳定性属性指示了它之前可用的特性名称和该特性稳定的版本。

如果一个项不稳定呢?不稳定的项非常有用,因为它们允许crate在不提供保证的情况下实验API。LazyLock目前是不稳定的。属性显示了用于启用LazyLock使用的特性名称,更重要的是,它显示了issue编号。这允许用户准确知道在哪里查看当前状态,甚至更好地提供反馈。

值得注意的是,每个公共项如果在crate中的任何地方使用,都需要一个稳定性属性。这是为了确保一切要么是稳定的,要么是不稳定的。不可能既不是稳定也不是不稳定的。

话虽如此,不可能自由使用不稳定的项。不稳定的项是选择加入的,需要一个特性门。如果你尝试不正确地使用一个不稳定的项,你会得到一个编译器错误。避免这个错误的唯一方法是在crate层面添加一个特性门。

有一些边缘情况需要考虑,比如当一个不稳定的项在稳定上下文中使用时,比如trait约束。这是允许的,但绝对应该有一个lint来确保这是deliberate的,因为如果不小心处理,对下游用户来说会令人困惑。

stable和unstable属性处理一个项是否总体上稳定,但还有const_stable和const_unstable属性来处理函数是否保证是const函数。

稳定性属性在Rust世界中已经存在很长时间了。2014年10月,在一篇官方博客文章中说,库作者可以继续使用稳定性属性。自那以后已经过去了将近9年,然而与那篇文章相反,稳定性属性目前明确仅用于标准库。尝试在其他crate中使用它们会导致编译器发出警告。我认为是时候最终为每个人提供这个功能了,因为我们知道它非常有用。

这确实是很多内容。在这些方面实际上做了什么呢?令人惊讶的是,已经做了相当多的工作。与Cargo的集成正在由专门为此目的而存在的工作组进行。它在nightly上可用,处于可用状态。


减少语言项和内部函数的数量是一个目标,但还没有完成任何工作。我打算研究一些更简单的情况,包括前面提到的那些。我相信其中一些可以t被简单地消除。

deprecated项的建议没有正式提案,但它已经实现了一段时间。它在nightly上可用,在deprecated_suggestion特性标志下。在稳定之前可能需要解决几个点,但不应该需要太多努力。

对于标准库中的未指定行为,这幸运地范围非常小。需要就期望的解决方案进行讨论,但任何实现都会很快跟进。

负面实现在nightly上实现,但该特性有已知的bug和边缘情况,必须在稳定之前解决。

特化不幸停滞了。据我所知,没有人在积极致力于此。然而,毫无疑问地,人们存在对特化的渴望。可能需要有类型理论知识的人来取得进展, 鉴于此,没有明确的时间表来解决问题,更不用说稳定该特性了。

至于crate Prelude,我实际上正在写一个RFC。正如熟悉这个过程的人所知,这需要一段时间。在被接受之前,更不用说实现和稳定,将会有大量的反馈和修订。

在我完成crate Prelude的RFC后,我将开始为稳定性属性写一个。这些属性在标准库中广泛使用,所以我们拥有实现这一点的能力和知识。稳定性属性有已知的限制,在广泛可用之前应该解决,但这是一个可以解决的问题。

总的来说,今天提到的许多项目已经有了工作,尽管工作程度不同。有些需要正式提案,而其他需要主题专家。它们都需要额外的工作。我个人正在尽我所能将标准库的有用功能带给每个人。我希望你们能分享我对这个目标的热情,并尽可能地协助实现它。

让标准库变得不那么特殊将需要大量的时间和努力,但这是Rust项目的一个总体和长期目标,整个Rust社区都将从中受益。

最后,屏幕上有大量信息。你可以在许多平台上找到我,包括GitHub和Mastodon,我的用户名是JH_Pratt。如果你有兴趣赞助我的工作,请这样做。我向你保证,这将是值得的。谢谢。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/871354.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大模型高效参数微调技术

文章目录 一、Fine-Tuning&#xff1a;微调二、Prompt-Tuning&#xff1a;提示调优2.1 工作原理2.2 PET (Pattern-Exploiting Training)2.3 Prompt-Tuning集成2.4 模板构建方式 三、Prefix Tuning&#xff1a;连续提示模板3.1 提出动机3.2 工作原理 四、P-Tuning V1/V24.1 P-Tu…

MQTT——Mosquitto使用(Linux订阅者+Win发布者)

前提&#xff1a;WSL&#xff08;Ubuntu22&#xff09;作为订阅者&#xff0c;本机Win10作为发布者。 1、Linux安装Mosquitto 命令行安装。 sudo apt-get install mosquitto 以上默认只安装了mosquitto的服务&#xff0c;不带测试客户端工具mosquitto_sub和mosquitto_pub。如…

楼栋管理助你打造智慧校园寝室新时代

在聚焦于智慧校园寝室管理的楼栋管理功能上&#xff0c;核心目标是实现对宿舍楼本身的高效、精细化运营。这一功能围绕楼栋信息维护、空间优化、安全监管等方面展开&#xff0c;旨在为学生创造一个安全、舒适的生活环境&#xff0c;同时提升管理效率。 楼栋管理功能首先建立在全…

Run LoongArch64 Alpine VM on x86_64

一、Build from source(build on x86_64) Obtain the latest libvirt, virt-manager, and qemu source code, compile and install them. 1.1 Build libvirt from source sudo apt-get update sudo apt-get install augeas-tools bash-completion debhelper-compat dh-apparm…

防火墙NAT实验(接上一个用认证实验)

目录 一、拓扑图 二、实验需求 三、实验步骤 需求1&#xff1a;办公区设备可以通过电信链路和移动链路上网(多对多的NAT&#xff0c;并且需要保留一个公网IP不能用来转换) 策略1&#xff1a;电信链路&#xff0c;多对多NAT&#xff0c;保留IP地址 测试策略1 策略2&#x…

2024年上半年信息系统项目管理师——综合知识真题题目及答案(第1批次)(4)

2024年上半年信息系统项目管理师 ——综合知识真题题目及答案&#xff08;第1批次&#xff09;&#xff08;4&#xff09; 第61题&#xff1a;The project manager should use &#xff08;tool for the purpose to report on the work remaining for projects. A. cumulativ…

内容协商源码解析与自定义 MessageConverter

目录 内容协商 1、引入xml依赖 2、postman分别测试返回json和xml 3、开启浏览器参数方式内容协商功能 4、内容协商原理 5、自定义 MessageConverter 综上 内容协商 根据客户端接收能力不同&#xff0c;返回不同媒体类型的数据。 若客户端无法解析服务端返回的内容&#…

keil5新建stm32工程的基本

1、建立工程文件夹&#xff0c;keil中新建工程&#xff0c;选择型号&#xff1b; 2、工程文件夹里建立自己所需要的文件夹等&#xff0c;复制固件库里面的文件到工程文件夹里&#xff1b; 3、将工程里建立对应的同名的分组&#xff0c;并将文件夹内的文件添加到工程分组中。 点…

Windows11终端winget配置

一、工具安装 Windows11是自带该工具的&#xff0c;如果wind10&#xff0c;可以找应用商店和GitHub上进行下载。 安装地址使用 winget 工具安装和管理应用程序 | Microsoft Learn 发布地址 Releases microsoft/terminal GitHub 二、无法使用问题排错 在命令行界面出现以…

CDN技术

CDN 假设你做了一个系统&#xff0c;要存放用户的一些信息&#xff0c;一般会把这些数据存放到MySQL当中&#xff0c;假设系统中有一些商品信息也是存放在MySQL中&#xff0c;慢慢的你的系统一天系统用户原来越多&#xff0c;查看商品的用户越来越多导致系统的响应速度越来越慢…

Real User ID 和 Effective User ID 的区别

在 Unix 和 Linux 系统中&#xff0c;每个进程都有多个用户标识符&#xff08;UID&#xff09;&#xff0c;其中最重要的是“真实用户 ID”&#xff08;real UID&#xff09;和“有效用户 ID”&#xff08;effective UID&#xff09;。 它们的区别和用途如下&#xff1a; 真实…

linux nethogs网络监控程序(端口监控、流量监控、上传流量、下载流量、进程监控进程网络)

文章目录 Nethogs 网络监控程序详解1. 引言2. Nethogs 的安装与运行2.1 安装 Nethogs- **Debian/Ubuntu**- **Fedora**- **Arch Linux** 2.2 运行 Nethogs 3. Nethogs 的使用详解3.1 基本界面- **PID**&#xff1a;进程的 ID。- **用户**&#xff1a;运行该进程的用户。- **程序…

注意力机制篇 | YOLOv8改进之在C2f模块引入Global Context注意力模块 | 全局上下文注意力机制

前言:Hello大家好,我是小哥谈。GCNet(Global Context Network)是由XV Jiарui开发的一个开源项目,它旨在通过全局上下文信息增强网络的注意力机制,以改善模型对图像的理解和处理能力。它的核心思想是在每个残差块之后添加一个全局上下文模块(即本文的Global Context注意…

【Quart 框架——来源于Flask的强大且灵活的异步Web框架】

目录 前言一、Quart简介1-1、简介1-2、与flask的区别 二、快速开始2-1、安装2-2、基本用法 三、核心功能3-1、异步路由3-2、WebSockets 支持3-3、中间件3-4、蓝图 (Blueprints) 四、部署4-1、使用uvicorn部署4-2、使用hypercorn部署 五、案例分析总结 前言 Quart 是一个基于 Py…

【Python】爬虫实战01:获取豆瓣Top250电影信息

本文中我们将通过一个小练习的方式利用urllib和bs4来实操获取豆瓣 Top250 的电影信息&#xff0c;但在实际动手之前&#xff0c;我们需要先了解一些关于Http 请求和响应以及请求头作用的一些知识。 1. Http 请求与响应 HTTP&#xff08;超文本传输协议&#xff09;是互联网上…

虚函数__

10 文章目录 虚函数虚函数表override(不允许后续函数继承)虚析构纯虚函数 虚函数 虚函数表 override(不允许后续函数继承) 虚析构 纯虚函数

从零开始学习PX4源码3(如何上传官网源码到自己的仓库中)

目录 文章目录 目录摘要1.将PX4源码上传至腾讯工蜂2.从腾讯工蜂克隆源码到本地ubuntu3.如何查看自己源码的版本信息 摘要 本节主要记录从零开始学习PX4源码3(如何上传官网源码到自己的仓库中)及如何查看PX4的固件版本信息&#xff0c;欢迎批评指正&#xff01; PX4源码版本V1.…

mysql-联合查询

一.联合查询的概念 .对于unio查询,就是把多次查询的结果合并起来,形成一个新的查询果集。 SELECT 字段列表 FROM 表A... UNION[ALL] SELECT 字段列表 FROM 表B...&#xff0c; 二.将薪资低于5000的员工,和年龄大于50岁的员工全部查询出来 select * from emp where salary&…

使用 Apache Pulsar 构建弹性可扩展的事件驱动应用

本视频来自 2024 Apache Pulsar 欧洲峰会&#xff0c;由 David Kjerrumgaard, 《Pulsar in Action》书作者给大家带来的《使用 Apache Pulsar 构建弹性可扩展的事件驱动应用》分享。 嘉宾&#xff5c;David Kjerrumgaard&#xff0c;Apache Pulsar Committer&#xff0c;《Pul…

总结单例模式的写法

一、单例模式的概念 1.1 单例模式的概念 单例模式&#xff08;Singleton Pattern&#xff09;是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式&#xff0c;它提供了一种创建对象的最佳方式。就是当前进程确保一个类全局只有一个实例。 1.2 单例模式的优…