Tải bản đầy đủ (.pdf) (6 trang)

O''''Reilly Network For Information About''''s Book part 56 ppt

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (21.53 KB, 6 trang )

boost::addable<simple_string,
boost::addable<simple_string,const char*> > {
The Difference Between Equality and Equivalence
When defining relational operators for classes, it's important to make the
distinction between equality and equivalence. An equivalence relation is required
in order to use the associative containers, and it defines a strict weak ordering
through the concept LessThanComparable.
[11]
This relation makes the least
assumptions, and poses as few requirements as possible, for types that are to be
used with the Standard Library containers. However, the difference between
equality and equivalence can sometimes be confusing, and it is important to
understand the difference. When a class supports the concept
LessThanComparable, it typically also supports the notion of equivalence. If two
elements are compared, and neither is less than the other, we can consider them to
be equivalent. However, equivalence doesn't necessarily mean equal. For example,
it may be reasonable to omit certain characteristics from a less than relation, but
consider them for equality.
[12]
To illustrate this, let's look at a class, animal,
which supports both an equivalence relation and an equality relation.
[11]
Capitalized concepts like LessThanComparable come straight from the C++
Standard. All of the concepts in Boost.Operators use lowercase names.
[12]
Which implies a strict weak ordering, but not a total ordering.
class animal : boost::less_than_comparable<animal,
boost::equality_comparable<animal> > {
std::string name_;
int age_;
public:


animal(const std::string& name,int age)
:name_(name),age_(age) {}
void print() const {
std::cout << name_ << " with the age " << age_ << '\n';
}
friend bool operator<(const animal& lhs, const animal& rhs) {
return lhs.name_<rhs.name_;
}
friend bool operator==(const animal& lhs, const animal& rhs) {
return lhs.name_==rhs.name_ && lhs.age_==rhs.age_;
}
};
Notice the difference between the implementation of operator< and that of
operator==. Only the animal's name is part of the less than relation, whereas
comparison of both the name and the age comprise the equality test. There is
nothing wrong with this approach, but it can have interesting ramifications. Let's
now put this class into action by storing some elements of the class in a
std::set. Just like other associative containers, set only relies on the concept
LessThanComparable. In the sample code that follows, we create four animals that
are all different, and then try to insert them into a set, all while pretending we
don't know that there is a difference between equality and equivalence.
#include <iostream>
#include <string>
#include <set>
#include <algorithm>
#include "boost/operators.hpp"
#include "boost/bind.hpp"
int main() {
animal a1("Monkey", 3);
animal a2("Bear", 8);

animal a3("Turtle", 56);
animal a4("Monkey", 5);
std::set<animal> s;
s.insert(a1);
s.insert(a2);
s.insert(a3);
s.insert(a4);
std::cout << "Number of animals: " << s.size() << '\n';
std::for_each(s.begin(),s.end(),boost::bind(&animal::print,_1));
std::cout << '\n';
std::set<animal>::iterator it(s.find(animal("Monkey",200)));
if (it!=s.end()) {
std::cout << "Amazingly, there's a 200 year old monkey "
"in this set!\n";
it->print();
}
it=std::find(s.begin(),s.end(),animal("Monkey",200));
if (it==s.end()) {
std::cout << "Of course there's no 200 year old monkey "
"in this set!\n";
}
}
Running the program produces the following, utterly nonsensical, output.
Number of animals: 3
Bear with the age 8
Monkey with the age 3
Turtle with the age 56
Amazingly, there's a 200 year old monkey in this set!
Monkey with the age 3
Of course there's no 200 year old monkey in this set!

The problem is not the age of the monkeyit very seldom isbut the failure to
distinguish between two related concepts. First, when the four animals (a1, a2,
a3, a4) are inserted into the set, the second monkey, a4, is actually not inserted
at all, because a1 and a4 are equivalent. The reason is that std::set uses the
expression !(a1<a4) && !(a4<a1) to decide whether there is already a
matching element. Because the result of that expression is true (our operator<
doesn't include the age), the insertion fails.
[13]
Then, when we ask the set to search
for a 200 year old monkey using find, it supposedly locates such a beast. Again,
this is because of the equivalence relation for animal, which relies on animal's
operator< and thus, doesn't care about age. We use find again to locate the
monkey in the set (a1), but then, to decide whether it matches, we call on
operator== and find that the monkeys don't match. It's not hard to understand
the difference between equality and equivalence when looking at these monkeys,
but it is imperative to know which one is applicable for a given context.
[13]
A set, by definition, does not contain duplicates.
Arithmetic Types
The Operators library is especially useful when defining arithmetic types. There
are many operators that must be defined for an arithmetic type, and doing it
manually is a daunting, tedious task, with plenty of opportunity for errors or
omissions. The concepts that are defined by the Operators library make it easy to
define only the bare minimum of operators for a class, and have the rest supplied
automagically. Consider a class that is to support addition and subtraction. Assume
that this class uses a built-in type for its implementation. Now add the appropriate
operators and be sure that they work with not only instances of that class, but also
with the built-in types that are convertible to the implementation type. You'll need
to provide 12 different addition and subtraction operators. The easier (and safer!)
approach, of course, is to use the two-argument form of the addable and

subtractable classes. Now suppose you need to add the set of relational
operators, too. You could probably add the 10 operators needed yourself, but by
now you know that the easiest thing is to use less_than_comparable and
equality_comparable. Having done so, you'd have 22 operators for the cost
of 6. However, you might also note that these concepts are common for value type
classes. Indeed, instead of using those four classes, you could just use additive
and totally_ordered.
We'll start by deriving from all four of the concept classes: addable,
subtractable, less_than_comparable, and
equality_comparable. The class, limited_type, just wraps a built-in
type and forwards any operation to that type. It limits the number of available
operations, providing just the relational operators and those for addition and
subtraction.
#include "boost/operators.hpp"
template <typename T> class limited_type :
boost::addable<limited_type<T>,
boost::addable<limited_type<T>,T,
boost::subtractable<limited_type<T>,
boost::subtractable<limited_type<T>,T,
boost::less_than_comparable<limited_type<T>,
boost::less_than_comparable<limited_type<T>,T,
boost::equality_comparable<limited_type<T>,
boost::equality_comparable<limited_type<T>,T >
> > > > > > > {
T t_;
public:
limited_type():t_() {}
limited_type(T t):t_(t) {}
T get() {
return t_;

}
// For less_than_comparable
friend bool operator<(
const limited_type<T>& lhs,
const limited_type<T>& rhs) {
return lhs.t_<rhs.t_;
}
// For equality_comparable
friend bool operator==(
const limited_type<T>& lhs,
const limited_type<T>& rhs) {
return lhs.t_==rhs.t_;
}
// For addable
limited_type<T>& operator+=(const limited_type<T>& other) {
t_+=other.t_;
return *this;
}
// For subtractable
limited_type<T>& operator-=(const limited_type<T>& other) {
t_-=other.t_;
return *this;
}
};
This is a good example of how easy the implementation becomes when using the
Operators library. Implementing the few operators that must be implemented to get
support for the full set of operators is typically not that hard, and the class becomes
much more understandable and maintainable than would otherwise have been the
case. (Even if implementing those operators is hard, you can concentrate on getting
just those few right.) The only potential problem with the class is the derivation

from eight different operator classes which, when using base class chaining, is not
as readable as one would like. We can greatly simplify our class by using
composite concepts instead.
template <typename T> class limited_type :
boost::additive<limited_type<T>,
boost::additive<limited_type<T>,T,
boost::totally_ordered<limited_type<T>,
boost::totally_ordered<limited_type<T>,T > > > > {
This is much nicer, and it does save some typing, too.
Use Operators Only When Operators Should Be Used

×