關於STL容器,最令人稱讚的特性之一就是是只要不超過它們的最大大小,它們就可以自動成長到足以容納你放進去的資料。(要知道這個最大值,只要調用名叫max_size的成員函數。)對於vector和string,如果需要更多空間,就以類似realloc的思想來增長大小。這個類似於realloc的操作有四個部分:
- 分配新的記憶體塊,它有容器目前容量的倍數。在大部分實現中,vector和string的容量每次以2為因數增長。也就是說,當容器必須擴充時,它們的容量每次翻倍。
- 把所有元素從容器的舊記憶體拷貝到它的新記憶體。
- 銷毀舊記憶體中的對象。
- 回收舊記憶體。
給了所有的分配,回收,拷貝和析構,你就應該知道那些步驟都很昂貴。當然,你不會想要比必須的更為頻繁地執行它們。如果這沒有給你打擊,那麼也許當你想到每次這些步驟發生時,所有指向vector或string中的迭代器、指標和引用都會失效時,它會給你打擊的。這意味著簡單地把一個元素插入vector或string的動作需要更新其他使用了指向vector或string中的迭代器、指標或引用的資料結構。
reserve成員函數允許你最小化必須進行的重新分配的次數,因而可以避免真分配的開銷和迭代器/指標/引用失效。但在我解釋reserve為什麼可以那麼做之前,讓我簡要介紹有時候令人困惑的四個相關成員函數。在標準容器中,只有vector和string提供了所有這些函數。
- size()告訴你容器中有多少元素。它沒有告訴你容器為它容納的元素分配了多少記憶體。
- capacity()告訴你容器在它已經分配的記憶體中可以容納多少元素。那是容器在那塊記憶體中總共可以容納多少元素,而不是還可以容納多少元素。如果你想知道一個vector或string中有多少沒有被佔用的記憶體,你必須從capacity()中減去size()。如果size和capacity返回同樣的值,容器中就沒有剩餘空間了,而下一次插入(通過insert或push_back等)會引發上面的重新分配步驟。
- resize(Container::size_type n)強制把容器改為容納n個元素。調用resize之後,size將會返回n。如果n小於當前大小,容器尾部的元素會被銷毀。如果n大於當前大小,新預設構造的元素會添加到容器尾部。如果n大於當前容量,在元素加入之前會發生重新分配。
- reserve(Container::size_type n)強制容器把它的容量改為至少n,提供的n不小於當前大小。這一般強迫進行一次重新分配,因為容量需要增加。(如果n小於當前容量,vector忽略它,這個調用什麼都不做,string可能把它的容量減少為size()和n中大的數,但string的大小沒有改變。在我的經驗中,使用reserve來從一個string中修整多餘容量一般不如使用“交換技巧”,那是條款17的主題。)[1]
這個簡介表示了只要有元素需要插入而且容器的容量不足時就會發生重新分配(包括它們維護的原始記憶體配置和回收,對象的拷貝和析構和迭代器、指標和引用的失效)。所以,避免重新分配的關鍵是使用reserve儘快把容器的容量設定為足夠大,最好在容器被構造之後立刻進行。
例如,假定你想建立一個容納1-1000值的vector<int>。沒有使用reserve,你可以像這樣來做:
vector<int> v;for (int i = 1; i <= 1000; ++i) v.push_back(i);
在大多數STL實現中,這段代碼在迴圈過程中將會導致2到10次重新分配。(10這個數沒什麼奇怪的。記住vector在重新分配發生時一般把容量翻倍,而1000約等於210。)
把代碼改為使用reserve,我們得到這個:
vector<int> v;v.reserve(1000);for (int i = 1; i <= 1000; ++i) v.push_back(i);
這在迴圈中不會發生重新分配。
在大小和容量之間的關係讓我們可以預言什麼時候插入將引起vector或string執行重新分配,而且,可以預言什麼時候插入會使指向容器中的迭代器、指標和引用失效。例如,給出這段代碼,
string s;...if (s.size() < s.capacity()) {s.push_back('x');}
push_back的調用不會使指向這個string中的迭代器、指標或引用失效,因為string的容量保證大於它的大小。如果不是執行push_back,代碼在string的任意位置進行一個insert,我們仍然可以保證在插入期間沒有發生重新分配,但是,與伴隨string插入時迭代器失效的一般規則一致,所有從插入位置到string結尾的迭代器/指標/引用將失效。
回到本條款的主旨,通常有兩情況使用reserve來避免不必要的重新分配。第一個可用的情況是當你確切或者大約知道有多少元素將最後出現在容器中。那樣的話,就像上面的vector代碼,你只是提前reserve適當數量的空間。第二種情況是保留你可能需要的最大的空間,然後,一旦你添加完全部資料,修整掉任何多餘的容量。