天天看點

<Effective STL>筆記--容器

1 容器

~~~~~~~

1.1 仔細選擇容器

=================

   1. C++中的容器回顧

      * 标準STL序列容器:

        vector、string、deque和list。

      * 标準STL關聯容器:

        set、multiset、map和multimap。

      * 非标準序列容器:

        slist(slist是一個單向連結清單)和rope(rope本質上是一個重型字元串)

      * 非标準關聯容器:

        hash_set、hash_multiset、hash_map和hash_multimap。

      * 幾種标準非STL容器:

        數組、bitset、valarray、stack、queue和priority_queue。

   2. 連續内容容器和基于節點的容器的差別

      * 連續内容容器(也叫基于數組的容器)在一個或多個(動态配置設定)的記憶體塊中保持它們的元素.

        連續記憶體容器包括vector,string和depue

      * 基于節點的容器在每個記憶體塊(動态配置設定)中隻儲存一個元素。

        基于節點的容器包括list,slist,所有的标準關聯容器(一般實作是用平衡樹實作的)和非标準的散列容器(不同的基于節點的實作)

   3. 選用何種容器的标準

      * 你關心元素在容器中的順序嗎?

        如果不,散列容器就是可行的選擇。否則,你要避免使用散列容器。

      * 你需要哪一類疊代器?

        如果必須是随機通路疊代器,在技術上你就隻能限于vector、deque和string,但你也可能會考慮rope。

        如果需要雙向疊代器,你就用不了slist和散列容器的一般實作

      * 容器中的資料的記憶體布局需要相容C嗎?

        如果是,你就隻能用vector

      * 你介意如果容器的底層使用了引用計數嗎?

        如果是,你就得避開string,因為很多string的實作是用引用計數。你也不能用rope,因為權威的rope實作是基于引用計數的。

        于是你得重新稽核你的string,你可以考慮使用vector<char>。

      * 你需要插入和删除的事務性語義嗎?也就是說,你需要有可靠地回退插入和删除的能力嗎?

        如果是,你就需要使用基于節點的容器。如果你需要多元素插入(比如,以範圍的方式)的事務性語義,你就應該選擇list,因為list是唯一提供多元素插入事務性語義的标準容器。

      * 你要把疊代器、指針和引用的失效次數減到最少嗎?

        如果是,你就應該使用基于節點的容器,因為在這些容器上進行插入和删除不會使疊代器、指針和引用失效(除非它們指向你删除的元素)。

        一般來說,在連續記憶體容器上插入和删除會使所有指向容器的疊代器、指針和引用失效。

      * 你需要具有有以下特性的序列容器嗎:1)可以使用随機通路疊代器;2)隻要沒有删除而且插入隻發生在容器結尾,指針和引用的資料就不會失效?

        這個一個非常特殊的情況,但如果你遇到這種情況,deque就是你夢想的容器。

        有趣的是,當插入隻在容器結尾時,deque的疊代器也可能會失效,deque是唯一一個“在疊代器失效時不會使它的指針和引用失效”的标準STL容器。

1.2 小心寫"與容器類型無關"的代碼

=================================

   1. 寫既要和序列容器又要和關聯容器一起工作的代碼并沒有什麼意義.

      因為很多成員函數隻存在于其中一類容器中.不同的容器是不同的,而且它們的優點和缺點有重大不同。它們并不被設計成可互換的,而且你做不了什麼包裝的工作。

   2. nultimap不存在operator[]

   3. 若存在改變容器類型的可能.

      可以使用typedef封裝容器類型.

      也可以建立一個類,在類中定義一個隐藏的容器對象.通過類的接口限制容器特殊資訊可見性的數量

1.3 使容器裡對象的拷貝操作輕量且正确

=====================================

   1. 當你從容器中擷取一個對象時,你所得到的對象不是容器裡的那個對象.而是拷貝.

      當你向容器中添加一個對象,進入容器的是你指定的對象的拷貝.

      拷進去,拷出來。這就是STL的方式.

   2. 在容器中插入删除操作或使用排序算法等會移動容器元素的操作,也會引發元素的拷貝.

   3. 把一個派生類對象插入基類對象的容器幾乎總是錯的,因為對象在拷貝入容器時會産生分隔

1.4 用empty來代替檢查size()是否為0

===================================

   1. 你應該首選empty的構造,而且理由很簡單:對于所有的标準容器,empty是一個常數時間的操作,但對于一些list實作,size花費線性時間。

1.5 盡量使用區間成員函數來代替它們的單元素兄弟

===============================================

   1. 盡量使用區間成員函數代替它們的單元素兄弟的理由。

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

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

      * 當處理标準序列容器時,應用單元素成員函數比完成同樣目的的區間成員函數需要更多地記憶體配置設定,更頻繁地拷貝對象,而且/或者造成多餘操作.

        1) 第一種稅在于沒有必要的函數調用。

           把numValues個元素插入v,每次一個,自然會花費你numValues次調用insert。

           而區間成員函數隻需一次調用開銷

        2) 無效率地把v中的現有元素移動到它們最終插入後的位置的開銷。

           每次調用insert來增加一個新元素到v,插入點以上的每個元素都必須向上移動一次來為新元素騰出空間。這就移動了numValues次.

           而區間成員函數隻需一次移動開銷

        3) 插入資料時可會會有記憶體配置設定的開銷.

           多次插入資料可能會有多次記憶體配置設定的開銷.

           區間配置設定使得隻需要配置設定一次記憶體

   2. string和vector在元素數目減少時,不會自動縮減記憶體配置設定

1.6 警惕C++最令人惱怒的解析

============================

   1. 幾乎任何東西都可能被分析成函數聲明.

  //假設你有一個int的檔案,你想要把那些int拷貝到一個list中。這看起來像是一個合理的方式:

      事實上,這聲明了一個函數data,它的傳回類型是list<int>。這個函數data帶有兩個參數:

      第一個參數叫做dataFile。它的類型是istream_iterator<int>。dataFile左右的括号是多餘的而且被忽略。

      第二個參數沒有名字。它的類型是指向一個沒有參數而且傳回istream_iterator<int>的函數的指針。

      正确的做法是

1.7 當使用new的指針的容器時,記得在銷毀容器後delete那些指針

===========================================================

   1. auto_ptr在C++标準中時禁止插入容器的,雖然有些編譯器允許容器中插入auto_ptr,但這是不可移植的.

   2. 由于auto_ptr複制方面的特性,在調用容器的方法後,可能會使原元素設為NULL

1.8 從容器中删除元素時,仔細選擇不同的政策

==========================================

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

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

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

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

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

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

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

      * 如果容器是标準關聯容器,使用remove_copy_if和swap,或寫一個循環來周遊容器元素,當你把疊代器傳給erase時記得後置遞增它。

        要注意,關聯容器中删除一個元素後,指向該元素的iterator會失效,故采用後置遞增.在iterator被删除前遞增,并将遞增前的值傳給erase

   3. 在循環内做某些事情(除了删除對象之外):

      * 如果容器是标準序列容器,寫一個循環來周遊容器元素,每當調用erase時記得都用它的傳回值更新你的疊代器。

        要注意,序列容器中删除一個元素後,指向該元素的iterator及之後的所有疊代器都會失效,但erase會傳回一個iterator,指向被删除元素的後一個元素.

      * 如果容器是标準關聯容器,寫一個循環來周遊容器元素,當你把疊代器傳給erase時記得後置遞增它。

1.9 注意allocator的協定和限制

==============================

   看不懂

1.10 了解自定義allocator的正确用法

1.11 對STL容器的線程安全性的期待現實一點

=========================================

   1. STL容器對多線程的支援為

      * 多個讀取者是安全的:

        多線程可能同時讀取一個容器的内容,這将正确地執行。當然,在讀取時不能有任何寫入者操作這個容器。

      * 對不同容器的多個寫入者是安全的:

        多線程可以同時寫不同的容器。