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

HandBooks Professional Java-C-Scrip-SQL part 54 doc

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.35 KB, 5 trang )

Usage
To start using the Operators library, implement the applicable operator(s) for your
class, include "boost/operators.hpp", and derive from one or more of the Operator
base classes (they have the same names as the concepts they help implement),
which all reside in namespace boost. Note that the inheritance doesn't have to be
public; private inheritance works just as well. In this usage section, we look at
several examples of using the different concepts, and also take a good look at how
arithmetic and relational operators work, both in C++ and conceptually. For the
first example of usage, we'll define a class, some_class, with an operator<. We
decide that the equivalence relation implied by operator< should be made available
through operator==. This can be accomplished by inheriting from
boost::equivalent.
#include <iostream>
#include "boost/operators.hpp"
class some_class : boost::equivalent<some_class> {
int value_;
public:
some_class(int value) : value_(value) {}
bool less_than(const some_class& other) const {
return value_<other.value_;
}
};
bool operator<(const some_class& lhs, const some_class& rhs) {
return lhs.less_than(rhs);
}
int main() {
some_class s1(12);
some_class s2(11);
if (s1==s2)
std::cout << "s1==s2\n";
else


std::cout << "s1!=s2\n";
}
The operator< is implemented in terms of the member function less_than. The
requirement for the equivalent base class is that operator< be present for the class
in question. When deriving from equivalent, we pass the derived classthat is,
some_classas a template parameter. In main, the operator== that is graciously
implemented for us by the Operators library is used. Next, we'll take a look at
operator< again, and see what other relations can be expressed in terms of less
than.
Supporting Comparison Operators
A relational operator that we commonly implement is less thanthat is, operator<.
We do so to support storage in associative containers and sorting. However, it is
exceedingly common to supply only that operator, which can be confusing to users
of the class. For example, most people know that negating the result of operator<
yields operator>=.
[1]
Less than can also be used to calculate greater than, and so on.
So, clients of a class supporting the less than relation have good cause for
expecting that the operators that must also (at least implicitly) be supported are
also part of the class interface. Alas, if we just add the support for operator< and
omit the others, the class isn't as usable as it could, and should, be. Here's a class
that's been made compliant with the sorting routines of the Standard Library
containers.
[1]
Although too many seem to think that it yields operator>!
class thing {
std::string name_;
public:
thing() {}
explicit thing(const std::string& name):name_(name) {}

friend bool operator<(const thing& lhs, const thing& rhs) {
return lhs.name_<rhs.name_;
}
};
This class supports sorting, and it can be stored in associative containers, but it
may not meet the expectations of the client! For example, if a client needs to know
whether thing a is greater than thing b, the client might write code like this:
// is a greater than b?
if (b<a) {}
Although this is just as correct, it doesn't convey the intent of the code clearly,
which is almost as important as the correctness. If the client needs to know whether
a is less than or equal to b, he would have to do this:
// is a less than, or equal to, b?
if (!(b<a)) {}
Again, the code is quite correct, but it will confuse people; the intent is certainly
unclear to most casual readers. It becomes even more confusing when introducing
the notion of equivalence, which we support (otherwise our class couldn't be stored
in associative containers).
// is a equivalent to b?
if (!(a<b) && !(b<a)) {}
Please note that equivalence is a different relation than equality, a topic which is
expanded upon in a later section. All of the aforementioned relational properties
are typically expressed differently in C++, namely through the operators that
explicitly perform the tests. The preceding examples should look like this (perhaps
with the exception of equivalence, but we'll let it pass for now):
if (a>b) {}
if (a<=b) {}
if (a==b) {}
The comments are now redundant, because the code says it all. As is, this code
doesn't compile, because the thing class doesn't support operator>, operator<=, or

operator==. But, as these operators (except operator==) can always be expressed
for types that implement the less_than_comparable concept, the Operators library
can help us out. All we need to do is to have thing derive from
boost::less_than_comparable, like so:
class thing : boost::less_than_comparable<thing> {
This gives you all the operators that can be implemented in terms of operator<, and
so, by just specifying a base class, the thing class now works as one would expect
it to. As you can see, when deriving thing from a class in the Operators library, we
must also pass thing as a template parameter to that base class. This technique is
discussed in the following section. Note that operator== is not defined for classes
supporting less_than_comparable, but there is another concept that we can use for
that one, namely equivalent. Deriving from boost::equivalent adds operator==, but
it should be duly noted that operator== is now defined in terms of an equivalence
relation, which in turn does not define equality. Equivalence implies a strict weak
ordering.
[2]
Our final version of the class thing looks like this:
[2]
If you're wondering what a strict weak ordering is, skip ahead to the next
section, but don't forget to return here later!
class thing :
boost::less_than_comparable<thing>,
boost::equivalent<thing> {
std::string name_;
public:
thing() {}
explicit thing(const std::string& name):name_(name) {}
friend bool operator<(const thing& lhs,const thing& rhs) {
return lhs.name_<rhs.name_;
}

};
This version only defines a single operator in thing's definition, which keeps the
definition concise, and by virtue of the inheritance from less_than_comparable and
equivalent, it provides quite an impressive set of useful operators.
bool operator<(const thing&,const thing&);
bool operator>(const thing&,const thing&);
bool operator<=(const thing&,const thing&);
bool operator>=(const thing&,const thing&);
bool operator==(const thing&,const thing&);
I'm sure you've seen many classes that provide a multitude of operators. Such class
definitions can be difficult to read because there are so many operator functions
declared/implemented. By inheriting from the concept classes in operators, you
provide the same interface but do so more clearly and with much less code.
Mentioning these concepts in the class definition makes it obvious for a reader
familiar with less_than_comparable and equivalent that the class supports the
aforementioned relational operations.
The Barton-Nackman Trick
In the two examples we've seen of inheriting from operator base classes, a strange-
looking construct feeds the derived class to its base class. This is a well-known
technique that is referred to as either the Barton-Nackmann trick
[3]
or the Curiously
Recurring Template Pattern.
[4]
The problem that this technique solves is that of a
cyclic dependency. Consider implementing a generic class that provides
operator== for other classes that define operator<. Incidentally, this is a concept
known as equivalent in this library (and mathematics, of course). Now, it is clear
that any class utilizing an implementation providing such services needs to know
about the enabling classlet's call it equivalent after the concept it helps implement.

However, it's just as clear that equivalent needs to know about the class for which
it should define operator==! This is a cyclic dependency, and at first glance, there's
no easy way out. However, if we make equivalent a class template, and add a
template parameter that designates the class for which to define operator==, we
have effectively injected the dependent typewhich is the derived classinto the
scope of equivalent. This example demonstrates the use of this idea.
[3]
"Invented" by John Barton and Lee Nackmann.
[4]
"Invented" by James Coplien.
#include <iostream>
template <typename Derived> class equivalent {
public:
friend bool operator==(const Derived& lhs,const Derived& rhs) {
return !(lhs<rhs) && !(rhs<lhs);
}
};
class some_class : equivalent<some_class> {
int value_;
public:
some_class(int value) : value_(value) {}
friend bool operator<(const some_class& lhs,
const some_class& rhs) {
return lhs.value_<rhs.value_;
}
};
int main() {
some_class s1(4);
some_class s2(4);
if (s1==s2)

std::cout << "s1==s2\n";
}
The base classequivalentaccepts a template argument that is the type for which it

×