1. Trang chủ
  2. » Công Nghệ Thông Tin

O''''Reilly Network For Information About''''s Book part 54 doc

5 220 0

Đang tải... (xem toàn văn)

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 5
Dung lượng 21,35 KB

Nội dung

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 . 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. 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. 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

Ngày đăng: 07/07/2014, 08:20