0%

Effective_STL 阅读笔记(二)

引言

空间配置器,可能是STL中最是默默无闻的组件了。但是,作为STL组件的基石,若是无法深入了解它,那么在学习STL中也会阻碍不断。有了前期的准备,那么再去了解一下vector与string的使用与规则,那便也是水到渠成的事情了。

空间配置与常用容器

STL基础空间配置器中,这里主要着重描述Allocator配置子,这里将从两个方面来描述Allocator,它的使用限制与使用场景。

Allocator使用限制

Allocator原先的作用是管理内存,但是容器设计中却没有得到应用。这就导致了一个问题,即多数容器不会使用配置子去申请空间内存。
Eg:

  1. 若是两个链表都通过alloctor申请了内存
  2. 使用过程中,list1持有了list2的元素
  3. 使用结束,析构list
  4. 必须先释放list2,再释放list1

那么问题就出现了,为什么list1可以调用list2的分配子去释放。那么这就是alloctor的限制,即分配子之间必须要等价,也就意味着分配子的所有成员都必须是静态成员。
对于配置子的限制可以归纳为以下几点:

  1. 分配子也是一个模板,模板参数T代表你为它分配内存的对象类型。
  2. 提供类型定义pointer与reference,但是始终让pointer为T*,reference为T&
  3. 不要让分配子拥有随着对象改变的状态(pre-object state)。通常,分配子不应为非静态成员
  4. 一定要提供rebind模板,因为标准容器依赖该模板。

Alloctor应用场景

当你需要手动去管理你的内存空间时,你就可以使用Allocator去处理。如共享内存场景。
当某个容器的都有相应的静态成员函数来执行内存分配与释放操作,使用静态成员管理内存的时,也可以使用Alloctor分配子
不过需要谨记分配子allocator的使用限制

多线程下的容器

多线下的容器,我们可以希望的可以有两点:

  1. 可以安全的读出
  2. 不同的线程可以对不同的容器进行写入

当然,这也都是希望,并非你的依赖。
多线程若要保证读写问题的正常,就需要为线程上锁。那么若是直接调用lock_guard,程序结束时也不一定会释放。但若是将锁封装进类中,通过RAII手法,即保证了锁在程序结束时一定会被释放,锁的正常使用也同样保证了线程的安全。

vector与string

相对于标准之中的数组,我们可以使用更倾向于去使用可以动态扩展的vector或string。
vector与string也是十分类似,它们在(需要的size > capacity)时,会自动的以2x的方式去扩容。
但这也导致了一个直接的问题,即它们的扩容方式:

  1. 开一个新空间,将原元素拷贝到新空间之中,
  2. 原有的迭代器失效,需要更新迭代器与指针。
  3. 析构原先的空间
  4. 释放旧内存

若是数据量小,还是可以接受的,如果是一个1000这样的数据,其扩容的次数将是2~10次。这样的代价是相对沉重的。
vector与string不断的扩容,对运行效能影响是巨大的。因此,STL提供了一个类似于普通数组的解决方案。那就是根据数据量预先分配指定大小的空间,这样就可以有效避免数据的复杂拷贝。

string

通常string 内部的实现是通过引用计数的方式来实现的,这样的好处是消除不必须的内存分配与不必须的内存拷贝。
但这同样埋下了多线程下使用string的坑,若是我在多线程中使用string,那么引用计数将会导致数据的死锁,从而引发程序的崩溃。
对于string的设计方式有许多,这样设计上的区别总结如下:

  1. string 的值可能被引用计数,也可能不会。(默认是会的)
  2. string对象大小的范围可以是一个char*指针大小的1~7倍
  3. 创建一个新的字符串可能需要零次、一次或两次的动态内存分配
  4. string 对象可能共享,也可能不共享其大小与容量

swap清除冗余

前文我们提及vector与swap由于扩容的因素,可能导致最后的capacity过大。若是想要空间上的浪费,我们最好的方法即是通过使用swap的方法去清除,从而做到shrink to fit。
方法:

  1. 使用一个临时变量去置换目标值
  2. 由于目标值的因素,不会存在多余的空间
  3. 交换完成后,数据空间被收缩,临时变量被销毁

vector容器

vectorans 作为容器它需要具备两个条件:

  1. ans必须是包含对象T的容器
  2. ans必须支持operator[]
    然而,实际情况上是,vector并非存储了bool,而是采用了类似于bitfield的概念。vector中的一个字节可以存储8个bool(仅占用一个bit),这也是vector节省空间的结果。
    当然,如果你果真有去存储bool这样的需求,你可以使用deque或bitset去进行存储,它们具有你想让vector完成的所有功能。

结语

vecotr与string 都是日常开发时,最常使用的容器。除了熟练的掌握之外,对于其内里的实现与使用规则也是需要深入理解的。