Effective STL (一)

來源:互聯網
上載者:User

條款1:仔細選擇你的容器

deque是唯一一個“在迭代器失效時不會使它的指標和引用失效”的標準STL容器。

 

條款2:小心對“容器無關代碼”的幻想

既要和序列容器又要和關聯容器一起工作的代碼並沒有什麼意義。很多成員函數只存在於其中一類容器中,比如,只有序列容器支援push_front或push_back,只有關聯容器支援count和lower_bound。 在不同的類中,相同的操作名稱,但在意義上是天差地別的。

 

條款3:使容器裡對象的拷貝操作輕量而正確

拷貝對象是STL的方式。即,比如向容器中添加對象,都是通過值來傳遞的,即都要對對象進行拷貝。

還有其它的操作,如排序演算法,刪除元素等,都要進行大量的拷貝動作。(通過容器裡面的對象的拷貝建構函式和拷貝賦值操作符來完成的)

如果你以基類對象建立一個容器,而你試圖插入衍生類別對象,那麼當對象(通過基類的拷貝建構函式)拷入容器的時候對象的派生部分會被刪除。

一個使拷貝更高效、正確而且對分割問題免疫的簡單的方式是建立指標的容器而不是對象的容器。

(但指標的容器有它們自己STL相關的頭疼問題。智能指標的容器是一個迷人的選擇)

 

條款4:用empty來代替檢查size()是否為0

對於所有的標準容器,empty是一個常數時間的操作,但對於一些list實現,size花費線性時間。

 

條款5:盡量使用區間成員函數代替它們的單元素兄弟

因為區間成員函數一般針對特定的容器進行了最佳化,要比“通用”版本的操作效率高。

無論何時你必須完全代替一個容器的內容,你就應該想到賦值。

幾乎所有目標區間被插入迭代器指定的copy的使用都可以用調用的區間成員函數的來代替。(盡量用成員函數來代替copy)

● 一般來說使用區間成員函數可以輸入更少的代碼。

● 區間成員函數會導致代碼更清晰更直接了當。

 

條款6:警惕C++最令人惱怒的解析

ifstream dataFile("ints.dat");

list<int> data(istream_iterator<int>(dataFile), // 警告!這完成的並不

istream_iterator<int>()); // 是像你想象的那樣

編譯器可能會將它解析為一個函數的聲明。

改用如下代碼來代替:(即在第一個參數外面加上括弧)

list<int> data((istream_iterator<int>(dataFile)), 

istream_iterator<int>()); 

不過這種方法可能並不是所有編譯器都支援的。

所以改成下面的方法可以保證所有編譯器支援:

istream_iterator<int> dataBegin(dataFile);

istream_iterator<int> dataEnd;

list<int> data(dataBegin, dataEnd);

 

條款7:當使用new得指標的容器時,記得在銷毀容器前delete那些指標

當你要刪除指標的容器時要避免資源泄漏,你必須用智能引用計數指標對象(比如Boost的shared_ptr)來代替指標,或者你必須在容器銷毀前手動刪除容器中的每個指標。

 

條款8:永不建立auto_ptr的容器

auto_ptr的容器(COAPs)是禁止的。試圖使用它們的代碼都不能編譯。

主要是因為auto_ptr會傳遞所有權,所以再對容器操作的時候,很可能產生一些非預期的行為。

 

條款9:在刪除選項中仔細選擇

● 去除一個容器中有特定值的所有對象:

如果容器是vector、string或deque,使用erase-remove慣用法。

如果容器是list,使用list::remove。

如果容器是標準關聯容器,使用它的erase成員函數。

● 去除一個容器中滿足一個特定判定式的所有對象:

如果容器是vector、string或deque,使用erase-remove_if慣用法。

如果容器是list,使用list::remove_if。

如果容器是標準關聯容器,使用remove_copy_if和swap,或寫一個迴圈來遍曆容器元素,當你把迭代器傳給erase時記得後置遞增它。

● 在迴圈內做某些事情(除了刪除對象之外):

如果容器是標準序列容器,寫一個迴圈來遍曆容器元素,每當調用erase時記得都用它的傳回值更新你的迭代器。

如果容器是標準關聯容器,寫一個迴圈來遍曆容器元素,當你把迭代器傳給erase時記得後置遞增它。

 

條款10:注意分配器的協定和約束

 

條款12:對STL容器執行緒安全性的期待現實一些

你能從已有的實現裡確定的最多是下列內容:

● 多個讀取者是安全的。多線程可能同時讀取一個容器的內容,這將正確地執行。當然,在讀取時不能有任何寫入者操作這個容器。

● 對不同容器的多個寫入者是安全的。多線程可以同時寫不同的容器。

所以要實現安全執行緒,必須自己來處理代碼,將要加鎖部分的代碼lock住。

 

條款13:盡量使用vector和string來代替動態分配的數組

無論何時,你發現你自己準備動態分配一個數組(也就是,企圖寫“new T[...]”),你應該首先考慮使用一個vector或一個string。(這樣就可以避免管理資源,省去了new及delete所可能造成的資源泄漏)

如果你在多線程環境中使用引用計數字串,就應該注意執行緒安全性支援所帶來的的效能下降問題。

如果你用的string實現是引用計數的,而且要在多線程環境中使用,可以用如下的方法嘗試:

一,看看庫是否可以關閉引用計數。

二,尋找或開發一個不使用引用計數的string實現。

三,考慮使用vector<char>來代替string。

 

條款14:使用reserve來避免不必要的重新分配

因為vector和string空間不足時(且小於max_size),每次會以2為因數增長。而且每次都會申請新記憶體,將舊資料拷貝到新記憶體,銷毀舊記憶體的對象,釋放舊記憶體。所以消耗很大。

用reserve來儲存足夠多的容量。如果確切的知道有多少元素,就可以使用reserve,或者保留你可能需要的最大空間。將資料全部添加完後,再修整掉多餘的內容。

 

條款15:小心string實現的多樣性

因為string的各實現不同,可能造成string特性的一些差異,如sizeof(string)就可能大小不一致。

新字串值的建立可能需要0、1或2次動態分配。

string對象可能是或可能不共用字串的大小和容量資訊。

string可能是或可能不支援每對象配置器。

不同實現對於最小化字元緩衝區的配置器有不同策略。

 

條款16: 如何將vector和string的資料傳給遺留的API

如果你有一個vector對象v,而你需要得到一個指向v中資料的指標,以使得它可以被當作一個數組,只要使用&v[0]就可以了。

讓C風格API把資料放入一個vector,然後拷到你實際想要的STL容器中的主意總是有效。

 

條款17:使用“交換技巧”來修整過剩容量

當vector擦除了很多元素之後,想要把它的大小縮減,以節省空間的。

reserve和resize都沒法減少vector的佔用空間。只能用swap來做。

如下:

vector<Contestant>(contestants).swap(contestants);

即,用目前contestants的有效元素來初始化一個臨時vector,然後再將兩個vector的內容互換。並且當這個臨時對象消失的時候,那個vector的所有空間都被釋放了。

string(s).swap(s); // 在s上進行“收縮到合適”

vector<Contestant>().swap(v); // 清除v而且最小化它的容量

string().swap(s); // 清除s而且最小化它的容量

 

條款18:避免使用vector<bool>

第一,它不是一個STL容器。

第二,它並不容納bool。

(雖然vector<bool>滿足大部分STL容器的必要條件,但仍然不能完全滿足。)

可以用deque<bool>來代替(deque<bool>的儲存並不連續)。或者用bitset來代替。(bitset是標準庫的一部分,但不是STL容器。)

bitset在編譯期間固定大小,所以不支援插入和刪除元素。也不支援iterator,使用一個壓縮的標記法,使得它包含的每個值只佔用一位元。它提供vector<bool>特有的flip成員函數及其它位集操作函數。

 

條款19:瞭解相等和等價的區別

a==b表示a和b相等。

!(a<b) && !(b<a) 即,a<b為假,且 b<a 也為假,則a和b等價。

只所以在關聯容器中使用等價,沒有使用相等的原因是:

如果關聯容器使用相等來決定兩個對象是否有相同的值,但因為關聯容器要決定元素間的順序,所以還是要有用來比較元素大小的運算子,這樣多個運算子,在關聯容器中容易造成混亂。

通過只使用一個比較函數並使用等價作為兩個值“相等”的意義的仲裁者,標準關聯容器避開了很多會由允許兩個比較函數而引發的困難。

 

條款20:為指標的關聯容器指定比較類型

在對關聯容器進行預設排序時,會預設採用指標的值來做為排序對象。所以此時要自訂定序。

 

條款21: 永遠讓比較函數對相等的值返回false

如果對相等的值返回true的時候,則在關聯容器裡面用operator <= 來比較兩個元素的時候,則會將兩個不相等的元素判斷為不相等。(因為!(a<b) && !(b<a)為true才相等)

而且對multiset及multimap等,使用equal_range來得到等價的值的範圍,a==b,但並不能得到a與b等價,所以它們兩個不可能都在equal_range得到的範圍內。

從技術上說,用於排序關聯容器的比較函數必須在它們所比較的對象上定義一個“嚴格的弱序化(strict weak ordering)”。任何一個定義了嚴格的弱序化的函數都必須在傳入相同的值的兩個拷貝時返回false。

 

條款22:避免原地修改set和multiset的鍵

set和multiset保持它們的元素有序,這些容器的正確行為依賴於它們保持有序。

如果要修改的話,將原有元素拷貝出來,然後修改這個拷貝值。刪除容器中的原元素,再將新的拷貝元素插入到容器裡面。

 

條款23:考慮用有序vector代替關聯容器

有序的vector與關聯容器相比,從演算法上來看,尋找速度可能不會快,但會節省空間的,因為關聯容器要儲存一些指標。

但實際上,因為關聯容器儲存元素的時候,元素是分散的,就可能儲存在多個記憶體頁面上,或者是儲存在虛擬記憶體中,所以用到元素的時候,會經常發生缺頁錯誤,從而導致頁面更頻繁的換入換出,影響尋找速度。

用有序vector來類比map或multimap時,map<const K, Val> 中的K不能為const,因為要對vector進行排序的時候,要改變K的值,來達到排序的效果。(類比map的話,因為vector儲存的是pair,所以要自訂排序函數)

 

條款24:當關乎效率時應該在map::operator[]和map-insert之間仔細選擇

map::operator[]被設計為簡化“添加或更新”功能,與vecotr、string等的operator[]無關,也與內建數組沒有聯絡。

map<int, Widget> m;

m[1] = 1.50;  //使用operator[]

m.insert(map<int, Widget>::value_type(1, 1.50));  //使用insert

使用:m[1] = 1.50; 時,如果1還未在map中出現,就會插入這個元素。

當插入元素的過程中(即1還未在map中出現),第一種方法會比第二種方法多出如下的三步:

建立臨時的預設構造Widget對象。

對Widget的賦值操作。

銷毀這個臨時Widget對象。

當1已經在map中出現,就會更新這個值。

這個時候,第一種方法反而會更高效。原因如下:

在insert方法中出現的map<int, Widget>是一個pair對象,在這個地方就會構造和析構pair,而且還會構造Widget,所以此時效率就比operator[]低。

不過我們自己可以實現一個演算法,針對不同的情況調用不同的函數來提高效率,使其總是高效的。

 

條款25:熟悉非標準散列容器

散列容器是關聯容器。

事實上的標準名字:hash_set、hash_multiset、hash_map和hash_multimap。(目前還未標準化,但下個版本會加入標準庫。不同的廠家實現原理、細節可能會不同)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.