Effective STL條款21

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

讓我向你展示一些比較酷的東西。建立一個set,比較類型用less_equal,然後插入一個10:


現在嘗試再插入一次10:


對於這個insert的調用,set必須先要判斷出10是否已經位於其中了。 我們知道它是,但set可是木頭木腦的,它必須執行檢查。爲了便於弄明白髮生了什麼,我們將一開始已經在set中的10稱爲10A,而正試圖插入的那個10叫10B。

set遍歷它的內部數據結構以查找哪兒適合插入10B。最終,它總要檢查10B是否與10A相同。關聯容器對“相同”的定義是等價(equivalence)(參見條款19),因此set測試10B是否等價於10A。當執行這個測試時,它自然是使用set的比較函數。在這一例子裏,是operator<=,因爲我們指定set的比較函數爲less_equal,而less_equal意思就是operator<=。於是,set將計算這個表達式是否爲真:


哦,10A和10B都是10,因此,10A <= 10B 肯定爲真。同樣清楚的是,10B <= 10A。於是上述的表達式簡化爲


再簡化就是


結果當然是false。 也就是說,set得出的結論是10A與10B不等價,因此不一樣,於是它將10B插入容器中10A的旁邊。在技術上而言,這個做法導致未定義的行爲,但是通常的結果是set以擁有了兩個爲10的值的拷貝而告終,也就是說它不再是一個set了。通過使用less_equal作爲我們的比較類型,我們破壞了容器!此外,任何對相等的值返回true的比較函數都會做同樣的事情。根據定義,相等的值卻不是等價的!是不是很酷?

OK,也許你對酷的定義和我不一樣。就算這樣,你仍然需要確保你用在關聯容器上的比較函數總是對相等的值返回false。但是,你需要保持警惕。對這條規則的違反容易達到令人吃驚的後果。

舉個例子,條款20描述了該如何寫一個比較函數以使得容納string*指針的容器根據string的值排序,而不是對指針的值排序。那個比較函數是按升序排序的,但我們現在假設你需要string*指針的容器的降序排序的比較函數。自然是抓現成的代碼來修改了。如果你不細心,可能會這麼幹,我已經加亮了對條款20中代碼作了改變的部分:


這裏的想法是通過將比較函數內部結果取反來達到反序的結果。很不幸,取反“<”不會給你(你所期望的)“>”,它給你的是“>=”。而你現在知道,因爲它將對相等的值返回true,對關聯容器來說,它是一個無效的比較函數。

你真正需要的比較類型是這個:


要避免掉入這個陷阱,你所要記住的就是比較函數的返回值表明的是在此函數定義的排序方式下,一個值是否大於另一個。相等的值絕不該一個大於另一個,所以比較函數總應該對相等的值返回false。

唉。

我知道你在想什麼。你正在想,“當然,這對set和map很有意義,因爲這些容器不能容納複本。但是multiset和multimap怎麼樣呢?那些容器可以容納複本,那些容器可能包含副本,因此,如果容器認爲兩個值相等的對象不等價,我需要注意些什麼?它將會把兩個都存儲進去的,這正是multi系列容器的所要支持的事情。沒有問題,對吧?”

錯。想知道爲什麼,讓我們返回頭去看最初的例子,但這次使用的是一個mulitset:


現在,s裏有兩個10的拷貝,因此我們期望如果我們在它上面做一個equal_range,我們將會得到一對指出包含這兩個拷貝的範圍的迭代器。但那是不可能的。equal_range,雖然叫這個名字,但不是指示出相等的值的範圍,而是等價的值的範圍。在這個例子中,s的比較函數說10A和10B是不等價的,所以不可能讓它們同時出現在equal_range所指示的範圍內。

你明白了嗎?除非你的比較函數總是爲相等的值返回false,你將會打破所有的標準關聯型容器,不管它們是否允許存儲複本。

從技術上說,用於排序關聯容器的比較函數必須在它們所比較的對象上定義一個“嚴格的弱序化(strict weak ordering)”。(傳給sort等算法(參見條款31)的比較函數也有同樣的限制)。如果你對嚴格的弱序化含義的細節感興趣,可在很多全面的STL參考書中找到,比如Josuttis的《The C++ Standard Library》[3](譯註:中譯本《C++標準程序庫》P176),Austern的《Generic Programming and the STL》(譯註:中譯本《泛型程序設計與STL》)[4],和SGI STL的網站[21]。 我從未發現這個細節如此重要,但一個對嚴格的弱序化的要求直接指向了這個條款。那個要求就是任何一個定義了嚴格的弱序化的函數都必須在傳入相同的值的兩個拷貝時返回false。

嗨! 這就是這個條款!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章