第13章 隐式内存管理通用容器

上一章中,我们实现了一个可以运行的(尽管很简单)类似 std::vector<T> 的类型 Vector<T>,以及一个同样可以运行(同样很简单)的类似 std::forward_list<T> 的类型 ForwardList<T>。

对于我们的 Vector<T> 类型来说,在最初实现了一个可以运行但有时效率不高的版本之后,我们努力将内存分配与对象构造分离。这种做法减少了运行时不必要的重复操作,但也带来了实现上更高的复杂度。在这一更复杂的实现中,区分了底层存储中“已初始化”和“未初始化”的部分,并对这两部分进行了适当的操作 —— 即把对象当作对象处理,把原始内存当作原始内存处理。例如,使用赋值操作(以及依赖赋值操作的算法)来替换已有对象的内容;而在原始内存中创建新对象时,则更倾向于使用placement new(以及依赖这一机制的算法)。

上一章中我们实现的 Vector<T> 是一个由大量源码构成的类。这种情况的一个原因是显式地进行内存管理。确实,我们让 Vector<T> 对象同时负责底层内存块的管理,以及其中存储对象的管理,而这种双重责任也带来了相应的代价。

在本章中,将重新审视这一设计,使内存管理变得“隐式化”,并讨论这种新方法所带来的影响。希望亲爱的读者,能从中看到代码实践中的潜在简化与优化。

本章目标如下:

我们将把大部分精力集中在重新审视 Vector<T> 容器上,但也会回顾 ForwardList<T>,看看是否能将同样的设计思路应用到这两种容器类型中。到本章结束时,至少对于 Vector<T> 来说,仍将拥有一个手写的、能够高效管理内存、并能区分原始内存与已构造对象的容器,但其实现将比第 12 章中的版本要简洁得多。

关于 Vector<T>,本章将对比两个版本。一个称为“简单版本”(naïve version),也就是我们最初在底层存储中始终使用 T 类型对象的实现;另一个称为“复杂版本”(sophisticated version),即后来将底层存储视为由两个(可能是空的)“区域”组成的实现:前一部分是 T 类型的对象,后一部分是原始内存。