- 为了支持快速随机访问,vector将元素连续存储,每个元素紧挨着前一个元素存储。通常情况下,我们不必关心一个标准库类型是如何实现的,而只需关心它如何使用。然而,对于vector和string,其部分实现渗透到了接口中。
- 假定容器中元素是连续存储的,且容器的大小是可变的,考虑向vector或string中添加元素会发生什么:如果没有空间容纳新元素,容器不可能简单地将它添加到内存中其他位置--因为元素必须连续存储。容器必须分配新的内存空间来保存已有元素和新元素,将已有元素从旧位置移动到新空间中,然后添加新元素,释放旧存储空间。如果我们每添加一个新元素,vector就执行一次这样的内存分配和释放操作,性能会慢到不可接受。为了避免这种代价,标准库实现者采用了可以减少容器空间重新分配次数的策略。当不得不获取新的内存空间时,vector和string的实现通常会分配比新的空间需求更大的内存空间。容器预留这些空间作为备用,可用来保存更多的新元素(提前分配更大的空间)。这样,就不需要每次添加新元素都重新分配容器的内存空间了。这种分配策略比每次添加新元素时都重新分配容器内存空间的策略要高效得多。其实际性能也表现得足够好--虽然vector在每次重新分配内存空间时都要移动所有元素,但使用此策略后,其扩张操作通常比list和deque还要快。
管理容量的成员函数
- 如表9.10所示,vector和string类型提供了一些成员函数,允许我们与它的实现中内存分配部分互动。capacity操作告诉我们容器在不扩张内存空间的情况下可以容纳多少个元素。reserve操作允许我们通知容器它应该准备保存多少个元素。
- 有当需要的内存空间超过当前容量时,reserve调用才会改变vector的容量。如果需求大小大于当前容量,reserve至少分配与需求一样大的内存空间(可能更大)。如果需求大小小于或等于当前容量,reserve什么也不做。特别是,当需求大小小于当前容量时,容器不会退回内存空间。因此,在调用reserve之后,capacity将会大于或等于传递给reserve的参数。这样,调用reserve永远也不会减少容器占用的内存空间。类似的,resize成员函数(参见9.3.5节,第314页)只改变容器中元素的数目,而不是容器的容量。我们同样不能使用resize来减少容器预留的内存空间。
- 在新标准库中,我们可以调用shrink_to_fit来要求deque、vector或string退回不需要的内存空间。此函数指出我们不再需要任何多余的内存空间。但是,具体的实现可以选择忽略此请求。也就是说,调用shrink_to_fit也并不保证一定退回内存空间。
capacity和size
- 理解capacity和size的区别非常重要。容器的size是指它已经保存的元素的数目;而capacity则是在不分配新的内存空间的前提下它最多可以保存多少元素。下面的代码展示了size和capacity之间的相互作用
vector<int> ivec;
//size 应该为0 capacity应该依赖于具体实现
cout << "ivec:size: " <<ivec.size()<< "capacity: " <<ivec.capacity() << endl;
//向ivec 添加24个元素
for(vector<int>::size_type ix = 0; ix != 24; ix++){ivec.push_back(ix);
}
//size 应该为24 capacity应该大于等于24 具体依赖于标准库的实现
cout << "ivec:size: " <<ivec.size()<< "capacity: " <<ivec.capacity() << endl;
- 当在我们的系统上运行时,这段程序得到如下输出:
- ivec:size:0 capacity:0
- ivec:size:24 capacity:32
- 我们知道一个空vector的size为0,显然在我们的标准库实现中一个空vector的capacity也为0。当向vector中添加元素时,我们知道size与添加的元素数目相等。而capacity至少与size一样大,具体会分配多少额外空间则视标准库具体实现而定。在我们的标准库实现中,每次添加1个元素,共添加24个元素,会使capacity变为32。可以想象ivec的当前状态如下图所示:
- 现在可以预先分配一些额外的空间
- ivec.reserve(50);//将capacity的容量设置至少为50 可能会更大
- size的大小应该为24,capacity的大小应该大于等于50 具体依赖于标准库的实现
- 添加元素用光预留空间 while(ivec.size() != ivec.capacity()){ ivec.push_back(0);}
- 只要使用的空间没有超过vector的容量,就不需要为此重新分配空间
- 使用shrink_to_fit将vector超出当前大小的多余内存空间退回给系统
- ivec.shrink_to_fit(); 要求归还内存。这仅仅是一个请求,是否归还并无保证
- 每个vector实现都可以选择自己的内存分配策略。但是必须遵守的一条原则 ,是 :只有当迫不得已时才可以分配新的内存空间
- 只有在执行insert操作时size与capacity相等,或者调用resize或reserve时给定的大小超过当前capacity,vector才可能重新分配内存空间。会分配多少超过给定容量的额外空间,取决于具体实现。
- 虽然不同的实现可以采用不同的分配策略,但所有实现都应遵循一个原则:确保用push_back向vector添加元素的操作有高效率。从技术角度说,就是通过在一个初始为空的vector上调用n次push_back来创建一个n个元素的vector,所花费的时间不能超过n的常数倍