第12章 显式内存管理通用容器

从开始探索 C++ 中内存管理机制与技术的奇妙旅程以来,我们已经走过了很长一段路。从第 4 章到第 7 章,构建了一个非常有趣的工具箱,并在此基础上继续发展,并从中借鉴以解决未来可能遇到的新问题。这个工具箱现在已经包含了以下内容(但不限于这些):

我们尚未涉及的(非常重要!)内存管理之处是 —— 容器。这是一个非常有趣的话题,我们将通过三个不同的角度在三个不同的章节中进行探讨。

第一个角度是在容器中如何显式但高效地处理内存管理。本章将重点介绍这一点。在某些应用领域,开发者习惯于实现(或维护)自己的容器,而不是使用标准库提供的容器。这可能有多种原因:例如,所在组织可能有高度专业化的需求;或者组织过去对标准库容器的性能不满意,当时的标准实现效率不如预期,因而开发了自己的替代容器。在多年基于自定义容器编写代码之后,再回到标准库容器可能显得代价高昂。

第二个角度(相对简短一些)是在容器中如何以隐式但高效的方式处理内存管理,这将在本书的第 13 章中介绍,在那一章中会重新审视并简化本章中介绍的实现。

第三个角度(更为复杂和微妙)是容器中如何通过分配器(allocator)来管理内存,这将构成本书第 14 章的内容。

在本章中,将编写一个(简化的)类似于 std::vector<T> 的类,命名为 Vector<T>。我们将以此为契机来讨论异常安全性(这是一个非常重要的议题,尤其是在编写泛型代码时)。随后会发现,我们的实现效率非常低下,std::vector<T> 的效率将明显优于 Vector<T> 实现,至少对某些类型来说是如此。基于这一认识,将重新审视我们的设计,采用更高效的内存管理方式,在多个方面实现显著的改进,并介绍一些重要的底层标准内存管理设施,这些工具将(也确实会)让我们的开发过程变得更加轻松。

我们还将编写一个自制的类似于 std::forward_list<T> 的类,命名为 ForwardList<T>,因为基于节点的容器有一些特定的问题和考量,这些是类似vector的容器无法充分体现的。本章将实现一个“基础版”的单向链表,在第 13 章中会简要再来讨论它,然后在第 14 章中进行更深入的探讨。

在阅读完本章之后,各位读者将能够做到以下几点:

更广泛地说,将明白为什么 std::vector<T> 如此高效,以及这种类型在资源管理方面如此难以超越。还将初步了解基于节点的容器(如 std::forward_list<T>)所面临的挑战,尽管这些问题将在后续章节中进一步深入探讨。这并不说明不应该编写自己的容器(对于特定的使用场景,往往可以做得比通用解决方案更好),但的确将更清楚地了解为何(以及何时)要这么做,并知道需要投入多少努力。

全面性还是代表性

本书并不要求对内容进行穷举式的呈现或实现(毕竟书本作为一种实体有其篇幅限制!),本章也不例外……甚至可以说更是如此!要完整实现标准库中两个容器类型所提供的全部成员函数,将使本书的篇幅大幅增加 —— 而且所使用的标准库实现通常还涵盖了更多的边界情况(并提供了许多巧妙的优化),这些是本书无法企及的。因此,我们将尝试展示一组核心的成员函数,供读者们在此基础上进行扩展,而不是试图实现每一个可能的函数。