没错,STL 中有迭代器、算法和函数对象,但是对于大多数 C++ 程序员来说,最值得注意的还是容器。容器比数组功能更强大、更灵活。它们可以动态增长(和缩减),可以自己管理内存,可以记住自己包含了多少对象。它们限定了自己所支持的操作的复杂性。诸如此类的优点还有很多。不难理解它们为何如此受欢迎,因为相对于其竞争者,无论是来自其他库中的容器还是你自己编写的容器,其优越性是显而易见的。STL容器不是简单的好,而是确实很好。
本章讲述适用于所有 STL 容器的准则。随后几章将就特定类型的容器展开描述。本章内容包括:如何就你所面临的具体制约条件选择适当的容器类型;避免一种错误认知,即为一种类型的容器而编写的代码换了其他容器也能工作;对于容器中的对象,复制操作的重要性;当指针或 auto_ptr 被存放在容器中时会有什么样的困难;删除操作的细节;用定制的分配子能做什么以及不能做什么;是程序获得最高效率的窍门;以及在多线程环境中使用容器的一些考虑。
涉及的方方面面很多。别着急,饭要一口一口地吃。这些问题将分为几条,逐条下来,你一定会形成一些想法,并将这些想法应用到你正在编写的代码中。
标准 STL 序列容器:vector、string、deque 和 list。
标准 STL 关联容器:set、multiset、map 和 multimap。
非标准序列容器:slist 和 rope。
非标准的关联容器:hash_set、hash_multiset、hash_map、hash_multimap。
这些限制的根源在于,对不同类型的序列容器,使迭代器、指针和引用无效的规则是不同的。
STL 做了很多拷贝,但它总的设计思想是为了避免不必要的拷贝。
事实上,他总体的设计目标是为了避免创建不必要的对象。
empty 对所有的标准序列容器都是常数时间操作,而对一些 list 的实现,size 耗费线性时间。
通过使用区间成员函数,通常可以少写一些代码。
使用区间成员函数通常会得到意图清晰和更加直接的代码。
使用命名的迭代器对象与通常的 STL 程序风格相违背,但你或许觉得为了使代码对所有编译器都没有二义性,并且是维护代码的人理解起来更容易,这一代价是值得的。
STL 容器很智能,但没有智能到直到是否该删除自己所包含的指针的程度。
当你使用指针的容器,而其中的指针应该被删除时,为了避免资源泄漏,你必须用引用计数形式的智能指针对象代替指针,或者当容器被析构时手动删除其中的每个指针。
千万别创建包含 auto_ptr 的容器,即使你的 STL 平台允许你这样做也不行。
要有效的删除容器中的元素,除了调用 erase 之外还要做很多工作。
解决问题的最佳方法取决于如何识别要删除的对象、存储元素的容器类型,以及当删除时你想做什么。
你的分配子是一个模板,模板参数 T 代表你为它们分配内存的对象的类型。
提供类型定义 pointer 和 reference ,但是始终让 pointer 为 T*,reference 为 T&。
千万别让你的分配子拥有随对象而不同的状态。通常,分配子不应该有非静态的数据成员。
记住,传给分配子的 allocate 成员函数的是那些要求内存的对象的个数,而不是所需的字节数。同时要记住,这些函数返回 T* 指针,即使尚未拥有 T 对象被构造出来。
一定要提供嵌套的 rebind 模板,因为标准容器依赖该模板。
只要你遵守了同一类型的分配子必须是等价的这一限制要求,那么,当你使用自定义的分配子来控制通用的内存管理策略的时候,或者在聚集成员关系的时候,或者在使用共享内存和其他特殊堆的时候,就不会陷入麻烦。
对于一个 STL 实现你最多只能期望:
多个线程是安全的。
多个线程对不同的容器做写入操作是安全的。