Effective STL之條款2:謹防容器無關程式碼的假象 (轉)
Effective STL:namespace prefix = o ns = "urn:schemas--com::office" />
條款2:謹防容器無關程式碼的假象
(Item2: Beware the illusion of container-independent code.)
STL 是在泛化(generalization)的基礎上構造出來的。容器由陣列(arrays)泛化(generalized)而來,並且根據其容納的型別被引數化(parameterized)。演算法由(functions)泛化(generalized)而來,並且根據其使用的迭代器(iterators)型別被引數化(parameterized)。迭代器(iterators)由指標(Pointers)泛化(generalized)而來,並且根據其指向的物件(s)的型別被引數化(parameterized)。
這僅僅是個開端。各自獨立的容器型別泛化成序列式(sequence)和關聯式(associative)容器,相似的容器擁有相似的功能。標準的連續(contiguous-memory)容器(見條款1)提供隨機訪問迭代器(ran access iterators),而標準的基於節點(node-based)的容器(見條款1)提供雙向迭代器(bidirectional iterators)。序列式容器(Sequence containers)支援push_front和/或push_back,而關聯式容器(associative containers)不支援。關聯式容器(associative containers)提供時間為對數時間的成員函式:lower_bound,upper_bound和equal_range,但是序列式容器(Sequence containers)沒有提供。(譯註:標準演算法庫提供lower_bound,upper_bound和equal_range三個模板函式,但要求作為引數傳入的迭代器區間必須有序,換句話說就是首先要對容器內元素排序。)
隨著所有這些泛化過程的繼續,很自然的你想要更多的泛化。唔,這種態度值得稱道,當你在寫自己的容器,迭代器和演算法時,的確應該儘可能的使用泛化。除此之外,許多員嘗試著以一種不同的方式使用泛化。他們不是在他們的中增加特定的容器型別,而是試著去泛化容器的概念(notion),這樣他們就能,比方說使用vector的同時,還保留著以後用deque或list來替換vector的選擇權——所有這些無須改變任何相關程式碼。也就是說,他們力求寫出容器無關程式碼(container-independent code)。雖然這種泛化源於好的目的,但幾乎總是被誤導。
即使最熱情的容器無關程式碼(container-independent code)的鼓吹者也會很快意識到嘗試去寫在序列式和關聯式容器下都能工作的軟體幾乎沒有什麼意義。許多成員函式只存在於某種容器型別中,例如只有序列式容器支援push_front或push_back,而關聯式容器只支援count和lower_bound等等。即使像insert和erase這樣的基本操作也會隨著(容器)型別的不同而有各自的特點(signatures)和語義(semantics)。例如,當你在序列式容器中插入一個物件時,該物件就呆在所插入的位置不動,但是當你在關聯式容器中插入一個物件時,容器會移動物件,所處位置取決於容器排序順序。再例如,erase的一個過載版本(該版本以一個iterator作為引數),作為序列式容器的成員函式被時會返回一個新的iterator,但是作為關聯式容器的成員函式被呼叫時返回為void。(條款9用例子說明了這是如何影響你寫的程式碼的。)
假設你渴望寫出能夠與最普通的序列式容器(vector,deque和list)一起使用的程式碼。很明顯,你只能編寫適合它們能力的程式碼(即它們支援的操作的交集),這也就意味著你不能使用reserve或capacity(見條款14),因為deque和list不提供這兩個操作。同樣,list的使用意味著你要放棄operator[],並且你也只能使用雙向迭代器(bidirectional iterators)所支援的操作。這樣的話,也就意味著你必須遠離需要隨機訪問迭代器(random access iterators)支援的演算法,包括sort,stable_sort,partial_sort以及nth_element(見條款31)。
另一方面,你要支援vector的想法排除了push_front和pop_front的使用,vector和deque都對splice和sort的成員函式形式置之不理(vector和deque沒有splice和sort這兩個成員函式)。結合上面所講的條件(constraints),後一個限制意味著你不能在你的“泛化的序列式容器”上呼叫成員函式sort。
很容易就可以得出結論:如果你違反了任何一種限制,那麼至少有一種你希望可以使用的容器會導致編譯失敗,只要你的程式碼中使用了這種容器。要編譯的程式碼也會更加難以捉摸。
(前面所說問題的)罪魁禍首是不同的序列式容器都有各自的迭代器,指標和引用的失效規則。要寫出與vector,deque和list一起正確工作的程式碼,你必須假設,如果在某一容器內有一種操作會使迭代器,指標和引用失效,那麼該操作也應該會使其它所有容器內的迭代器,指標和引用失效。這樣,你就必須假設呼叫insert會使任何東西失效,這是因為deque::insert使所有的迭代器失效,另外在喪失呼叫capacity的能力的同時,你必須假設呼叫vector::insert會使所有的指標和引用失效。(條款1解釋了deque是唯一的一種有時會使迭代器失效而不會使指標和引用失效的容器。)相似的原因你也必須假設呼叫erase會使任何東西失效。
要更多的例子嗎?你不能傳遞容器內的資料給C介面,因為只有vector支援這種操作(見條款16)。你不能例項化以bool作為存貯物件型別的容器,這是因為(正如條款18解釋的)vector
當所有的約束條件都被考慮後,你就擁有了一個“泛化的序列式容器”,要使用這個容器,你不能呼叫reserve,capacity,operator[],push_front,pop_front,splice或者需要隨機訪問迭代器(random access iterators)支援的演算法;每次呼叫insert和erase要花費線性時間並且使所有的迭代器,指標和引用失效; 不相容於C,也不能儲存bools。這真的就是你希望在你的應用程式中使用的那種容器嗎?我懷疑不是。
如果你想控制一下你的野心,決定不再支援list,你還是要放棄reserve,capacity,push_front和pop_front的使用;你還是要假設每次呼叫insert和erase要花費線性時間並且使所有的一切失效;你還是會丟失與C佈局的相容性;你還是不能儲存bools。
如果你放棄了序列式容器,改變主意想要寫出能夠與所有關聯式容器一起工作的程式碼,情況也不會更好。同時為set和map編寫程式碼幾乎是不可能的,因為set儲存單一物件(single objects)而map儲存雙物件(pairs of objects)。即使是同時為set和multiset(或map和multimap)編寫程式碼也是困難的。sets/maps中insert成員函式有一個過載版本(該版本只需要一個引數值value)和其multi兄弟(意即multisets/multimaps)相比擁有不同的返回型別(譯註:sets/maps中的原型為:pair
面對事實吧:這不值得做(寫容器無關程式碼)。不同的容器是不同的,隨側重點的不同,它們各有優缺點。它們被設計出來不是用於互換的,不要想去忽略這一點。如果你嘗試的話,那麼你就是在挑釁命運,而命運不喜歡被挑釁。
同樣,當你意識到所選的容器不是最理想的時候,天快要破曉了,而你需要使用一種不同的容器型別。現在你知道當改變容器型別時,你不但要解決報告的問題,還要根據新容器的特徵和迭代器,指標和引用的失效規則檢查所有使用容器的程式碼,從而搞明白什麼需要改變。如果要從vector轉換到別的容器,你就要確保你不再依賴vector的C相容記憶體佈局,要轉換到vector,你要確保你不會再使用它來儲存bools。
既然不時的改變容器型別是不可避免的,你就可以使用一般的方法來使這種改變來得容易:透過封裝,封裝,還是封裝。最容易的一種方法就是廣泛的使用容器和迭代器型別的typedefs。因此,不要這樣寫:
class Widget {…};
vector
Widget bestWidget;
… // give bestWidget a value
vector
find( vw.begin(), vw.end(), bestWidget ); // value as bestWidget
這樣寫:
class Widget {…};
typedef vector
typedef WidgetContainer::iterator WCIterator;
WidgetContainer cw;
Widget bestWidget;
…
WCIterator i = find(cw.begin(), cw.end(), bestWidget);
這就使改變容器型別時非常容易,如果要改變的僅僅是新增一個自定義的分配器(allocator)時尤其方便。(這樣的改變不會影響iterator/pointer/reference的失效規則。)
class Widget {…};
template
SpecialAllocator {…}; // a template
typedef vector
typedef WidgetContainer::iterator WCIterator; // still works
WidgetContainer cw;
Widget bestWidget;
…
WCIterator i = find(cw.begin(), cw.end(), bestWidget); // still works
如果你不想要typedefs的封裝功能,你還是很可能會欣賞它們幫你所節約的工作。例如,你有這種型別的物件:
map vector CIStringCompare> // CIStringCompare is “case-insensitive // string compare;” Item 19 describes it 並且你想使用map的const_iterators,你真的想拼寫 map 多過一次嗎?一旦你使用了STL一段時間,你會意識到typedefs是你的好朋友。 typedef只是某種型別的同義詞,因此它所提供的封裝純粹是字面意義上的。typedef不能阻止客戶程式碼做(或依賴)任何它們已經不能再做(或依賴)的事。如果你想限制暴露給客戶所選容器的細節,你需要更好的武器。你需要的是類。 如果要替換容器的型別,你應當限制可能需要修改的程式碼,把容器隱藏在類的內部,儘量減少外部能夠訪問的特定容器資訊(透過類介面)。例如,如果你要建立一個客戶list,不要直接使用list。相反,去建立一個CustomerList類,並且把一個list隱藏在私有域中: class CustomerList { private: typedef list typedef CustomerContainer::iterator CCIterator; CustomerContainer customers; public: … // limit the amount of list-specific // information visible through this // interface }; 首先,這看起來似乎有點傻。畢竟客戶list是一個list,對嗎?恩,也許吧。不久你可能發現你不需要你曾期望的那樣頻繁的在list的中部插入(insert)和擦除(erase)客戶,但你卻需要地訪問前面20%的客戶——為演算法nth_element定製的任務(見條款31)。但是nth_element需要隨機訪問迭代器(random access iterators)。它不能與list一起使用。在這種情況下,你的客戶“list”可以用vector或deque更好的實現。 當你考慮這種改變時,你還是要檢查每個CustomerList的成員函式和友元,搞清楚它們是如何被影響的(在效能和iterator/pointer/reference失效等方面),但是如果你在封裝CustomerList的實現細節時做得很好,對CustomerList的客戶程式碼的影響應該是很小的。你不能寫出容器無關(container-independent)程式碼,但是它們(封裝的類)也許可以做到。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-959096/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Effective STL:Item2 當心與容器無關(container-independent)的程式碼這個錯覺 (轉)AI
- More Effective C++ 條款2 (轉)C++
- Effective Modern C++ 系列之 條款2: autoC++
- More Effective C++ 條款4 (轉)C++
- More Effective C++ 條款19 (轉)C++
- More Effective C++ 條款6 (轉)C++
- More effective C++ 條款14 (轉)C++
- More Effective C++ 條款15 (轉)C++
- More Effective C++ 條款3 (轉)C++
- More Effective C++ 條款11 (轉)C++
- More effective C++ 條款13 (轉)C++
- More effective C++ 條款12 (轉)C++
- More Effective C++ 條款5 (轉)C++
- More Effective C++ 條款一 (轉)C++
- More Effective C++ 條款17 (轉)C++
- More Effective C++ 條款18 (轉)C++
- More Effective C++ 條款20 (轉)C++
- More Effective C++ 條款21 (轉)C++
- More Effective C++ 條款22 (轉)C++
- More Effective C++ 條款23 (轉)C++
- More Effective C++ 條款24 (轉)C++
- More Effective C++ 條款25 (轉)C++
- More Effective C++ 條款7 (轉)C++
- More Effective C++ 條款8 (轉)C++
- More Effective C++ 條款28(中) (轉)C++
- More effective c++ 條款10(上) (轉)C++
- More effective c++ 條款10(下) (轉)C++
- More Effective C++ 條款27(下) (轉)C++
- More Effective C++ 條款28(上) (轉)C++
- More Effective C++ 條款28(下) (轉)C++
- More Effective C++ 條款26(下) (轉)C++
- Effective STL: Item 2: Beware the illusion of container-independent (轉)AI
- 初探STL容器之Vector
- c++標準程式庫:STL容器之mapC++
- STL 之 vector 容器詳解
- Guru of the Week 條款14:類之間的關係(上篇) (轉)
- Guru of the Week 條款15:類之間的關係(下篇) (轉)
- STL程式設計實踐一:謹慎使用下標運算子 (轉)程式設計