STL is full of comparing whether objects have the same value. For example, if you use find to locate the location of the first object with a specific value in the interval, find must be able to compare two objects to see if one value is equal to the other. Similarly, when you try to insert a new element to the set, set: insert must be able to determine whether the value of the element is already in the set.
The find algorithm and the insert member functions of the set algorithm are representative of many functions that must judge whether the two values are the same. However, they are completed in different ways. Find defines "same"Equal, Based on operator =. Set: insert defines "same"Equivalent, Usually based on operator <. Because there are different definitions, it is possible that one definition specifies that two objects have the same value while the other definition determines that they do not. If you want to use STL effectively, you must understand the differences between equality and equivalence.
In terms of operations, the concept of equality is based on operator =. If the expression "x = y" returns true, X and Y have equal values, otherwise they do not exist. This is straightforward, but keep in mind that the values of x and y do not mean that all their members have equal values. For example, we may have a widget class that records the last access internally.
class Widget {public:...private:TimeStamp lastAccessed;...};
We can have an operator for the widget to ignore this domain =:
Bool operator = (const widget & LHS, const widget & RHs) {// ignore the code of the lastaccessed domain}
Here, two widgets can have equal values even if their lastaccessed fields are different.
The equivalence is based on the relative position of the object value in an ordered interval. Equivalence is generally meaningful in sorting order, a part of each standard associated container (such as set, Multiset, map, and multimap. If two objects X and Y are not ranked before the other in the sorting order of the associated container C, their sorting order for C is equivalent. This sounds complicated, but in fact it doesn't. Consider, for example, a set <widget> S. The two widgets W1 and W2 have equivalent values for S if there is no previous one in the s sorting order. The default comparison function of set <widget> is less <widget>, while the default less <widget> function calls operator <, so W1 and W2 have an equivalent value for operator <if the following expression is true:
! (W1 <W2) // W1 <W2 when it is not true & // and! (W2 <W1) // It is not true when W2 <W1
This makes sense: If two values are not before the other (about a sorting standard), they are equivalent (according to that standard ).
Generally, the comparison function used to associate containers is not operator <or even less. It is a user-defined comparison function. (For more information, seeClause 39.) Each standard associated container uses its key_comp member function to access the sorted sort type. Therefore, if the following formula is evaluated as true, two objects X and Y have equivalent values for the sorting criteria of an associated container C:
! C. key_comp () (x, y )&&! C. key_comp () (Y, x) // in the C sorting order // If X is not true before y, // In the C sorting order at the same time // if y is not true before X
Expression! C. key_comp () (x, y) looks ugly, but once you know that C. key_comp () returns a function (or a function object), the ugliness disappears .! C. key_comp () (x, y) only calls the function (or function object) returned by key_comp, and uses X and Y as real parameters. Then the result is reversed. C. key_comp () (x, y) returns true only when X is prior to Y in the C sorting order! C. key_comp () (x, y) only when X is in the order of CNoY is true.
To fully understand the meaning of equality and equivalence, consider a case-insensitive set <string>, that is, the set comparison function ignores the case-insensitive set <string> in the string. Such comparison functions will regard "STL" and "STL" as equivalent.Clause 35Demonstrate how to implement a function, cistringcompare, Which is case-insensitive, but set requires a comparison FunctionTypeIs not a real function. To balance the gap, we write an operator () that calls the cistringcompare function class:
Struct cistringcompare: // class for case-insensitive public // string comparison; binary_function <string, String, bool >{// for information about this base class // seeClause 40Bool operator () (const string & LHS, const string & RHs) const {return cistringcompare (LHS, RHS); // For details about how cistringcompare} // seeClause 35}
Given cistringcompare, it is easy to create a case-insensitive set <string>:
set<string, CIStringCompare> ciss;// ciss = “case-insensitive// string set”
If we insert "Persephone" and "Persephone" to this set, only the first string is added, because the second is equivalent to the first one:
CISS. insert ("Persephone"); // Add a new element to the set. CISS. insert ("Persephone"); // no new element is added to the set.
If we use the find member function of set to search for the string "Persephone", the search will succeed,
If (CISS. Find ("Persephone ")! = CISS. End ()... // The test is successful.
However, if we use a non-member find algorithm, the search will fail:
If (find (CISS. Begin (), CISS. End (), "Persephone ")! = CISS. End ()... // This test will fail
That's because "Persephone" is equivalent to "Persephone" (about the comparative imitation function cistringcompare), but not equal to it (because string ("Persephone ")! = String ("Persephone ")). This example demonstrates why you should prioritize member functions (like set: Find) rather than non-member siblings (like find) following the recommendation in cla44.
You may wonder why the standard associated containers are equivalent rather than equal. After all, most programmers have a sense of equality and lack the sense of equivalence. (If this is not the case, you do not need these terms .) The answer seems simple at first glance, but the closer you look, the more problems you will find.
Each container must have a comparison function (less by default) defining how to keep the standard associated containers in order ). The equivalence is defined based on this comparison function. Therefore, users of the standard associated containers only need to specify a comparison function (the one that determines the sorting order) for any container they want to use ). If the associated container uses equality to determine whether two objects have the same value, then each associated container needs, except for the comparison function used for sorting, you also need a comparison function to determine whether two values are equal. (By default, this comparison function should probably be performance_to, but it is interesting that performance_to has never been used as a default comparison function in STL. When equality is required in STL, it is customary to simply call operator =. For example, this is done by a non-member find algorithm .)
Let's assume that we have a set-like STL container called set2cf, "set with two comparison functions ". The first comparison function is used to determine the order of the set, and the second is used to determine whether two objects have the same value. Now consider this set2cf:
set2CF<string, CIStringCompare, equal_to<string> > s;
Here, s internally sorts its strings without case-sensitivity considerations. This is intuitively equivalent: if one of the two strings is equal to the other, they have the same value. Let's Insert the spelling of Persephone to S:
s.insert("Persephone");s.insert("persephone");
What should I do? If we say "Persephone "! = "Persephone" and then both insert S. What sequence should they be? Remember that the sorting function cannot tell them separately. Can we insert data in any order, so we can discard the ability to traverse the set content in a definite order? (It cannot be determined that the sequential traversal of the associated container elements is already suffering from Multiset and multimap, because the standard does not specify the relative sequence of equivalent values (for Multiset) or keys (for multimap .) Or do we stick to a definite sequence of S content and ignore the second insertion attempt (the one of "Persephone )? If we do this, what will happen here?
If (S. Find ("Persephone ")! = S. End ()... // is this test successful or failed?
Probably find uses the equivalent check, but if we ignore the second insert call to maintain a definite sequence of elements in S, this find will fail, even if the insert of "Persephone" is ignored because it is a duplicate value principle!
In short, by using only one comparison function and using the equivalent value as the arbitration of the meaning of the two values "equal", the standard associated container avoids many difficulties caused by allowing two comparison functions. At first, the behavior may seem a bit strange (especially when you find that a member or a non-member find may return different results), but finally, it avoids confusion caused by mixing equality and equivalence in standard associated containers.
Interestingly, once you leaveOrderedThe domain of the associated container changes, and the equal-to-equivalent problem -- already -- re-emerged. There are two general designs for non-standard (but common) associated containers based on the hash. One design is based on equality, and the other is based on equivalence. I encourage you to goClause 25Learn more such containers and designs to determine which container to use.