CSDN博客

Effective STL: Item 21: Always have comparison functions return

发表于2001/6/6 22:07:00 622人阅读

分类：
C++

Let me show you something kind of cool. Create a set where less_equalis the comparison type, then insert 10 into the set:set<int, less_equal<int> > s; // s is sorted by “<=”s.insert(10); // insert the value 10Now try inserting 10 again:s.insert(10);For this call to insert, the set has to figure out whether 10 is alreadypresent. We know that it is, but the set is dumb as toast, so it has tocheck. To make it easier to understand what happens when the setdoes this, we’ll call the 10 that was initially inserted 10 A and the 10that we’re trying to insert 10 B .The set runs through its internal data structures looking for the placeto insert 10 B . It ultimately has to check 10 B to see if it’s the same as10 A . The definition of “the same” for associative containers is equiva-lence(see Item 19), so the set tests to see whether 10 B is equivalent to10 A . When performing this test, it naturally uses the set’s comparisonfunction. In this example, that’s operator<=, because we specifiedless_equal as the set’s comparison function, and less_equal means oper-ator<=.The set thus checks to see whether this expression is true:!(10 A <= 10 B ) && !(10 B <= 10 A ) // test 10 A and 10 B for equivalenceWell, 10 A and 10 B are both 10, so it’s clearly true that 10 A <= 10 B .Equally clearly, 10 B <= 10 A . The above expression thus simplifies to!(true) && !(true)and that simplifies tofalse && falsewhich is simply false. That is, the set concludes that 10 A and 10 B arenot equivalent, hence not the same,and itthus goes aboutinserting10 B into the container alongside 10 A . Technically, this action yieldsundefined behavior, but the nearly universal outcome is that the setends up with two copies of the value 10, and that means it’s not a setany longer. By using less_equal as our comparison type, we’ve cor-ruptedthe container! Furthermore, any comparison function whereഊequal values return true will do the same thing. Equal values are, bydefinition, not equivalent! Isn’t that cool?Okay, maybe your definition of cool isn’t the same as mine. Even so,you’ll still want to make sure that the comparison functions you usefor associative containers always return false for equal values. You’llneed to be vigilant, however. It’s surprisingly easy to run afoul of thisconstraint.For example, Item 20 describes how to write a comparison functionfor containers of string* pointers such that the container sorts its con-tentsby the values of the strings insteadofthe valuesofthepointers.That comparison function sorts them in ascending order, but let’ssuppose you’re in need of a comparison function for a container ofstring* pointers that sorts in descending order. The natural thing to dois to grab the existing code and modify it. If you’re not careful, youmight come up with this, where I’ve highlighted the changes to thecode in Item 20:struct StringPtrGreater: // highlights show howpublic binary_function<const string*, // this code was changedconst string*, // from page 89. Beware,bool> { // this code is flawed!bool operator()(const string *ps1, const string *ps2) const{return !( *ps1 < *ps2); // just negate the old test;} // this is incorrect!};The idea here is to reverse the sort order by negating the test insidethe comparison function. Unfortunately, negating “<” doesn’t give you“>” (which is what you want), it gives you “>=”. And you now under-standthat “>=”, because it will return true for equal values, is aninvalid comparison function for associative containers.The comparison type you really want is this one:struct StringPtrGreater: // this is a validpublic binary_function<const string*, // comparison type forconst string*, // associative containersbool> {bool operator()(const string *ps1, const string *ps2) const{return *ps2 < *ps1; // return whether *ps2} // precedes *ps1 (i.e., swap// the order of the}; // operands)ഊTo avoid falling into this trap, all you need to remember is that thereturn value of a comparison function indicates whether one valueprecedes another in the sort order defined by that function. Equal val-uesnever precede one another, so comparison functions shouldalways return false for equal values.Sigh.I know what you’re thinking. You’re thinking, “Sure, that makes sensefor set and map, because those containers can’t hold duplicates. Butwhat about multiset and multimap? Those containers may containduplicates, so what do I care if the container thinks that two objects ofequal value aren’t equivalent? It will store them both, which is whatthe multi containers are supposed to do. No problem, right?”Wrong. To see why, let’s go back to the original example, but this timewe’ll use a multiset:multiset<int, less_equal<int> > s; // s is still sorted by “<=”s.insert(10); // insert 10 As.insert(10); // insert 10 Bs now has two copies of 10 in it, so we’d expect that if we do anequal_range on it, we’ll get back a pair of iterators that define a rangecontaining both copies. But that’s not possible. equal_range,itsnamenotwithstanding, doesn’t identify a range of equal values, it identifiesa rangeofequivalent values. In this example, s’s comparison functionsays that 10 A and 10 B are not equivalent, so there’s no way that bothcan be in the range identified by equal_range.You see? Unless your comparison functions always return false forequal values, you break all standard associative containers, regard-lessof whether they are allowed to store duplicates.Technically speaking, comparison functions used to sort associativecontainers must define a “strict weak ordering” over the objects theycompare. (Comparison functions passed to algorithms like sort (seeItem 31) are similarly constrained.) If you’re interested in the details ofwhat it means to be a strict weak ordering, you can find them in manycomprehensive STL references, including Josuttis’ The C++ StandardLibrary [3], Austern’s Generic Programming and the STL [4], and theSGI STL Web Site [21]. I’ve never found the details terribly illuminat-ing,but one of the requirements of a strict weak ordering bearsdirectly on this Item. That requirement is that any function defining astrict weak ordering must return false if it’s passed two copies of thesame value.Hey! That is this Item!