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

O''''Reilly Network For Information About''''s Book part 57 docx

6 230 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 6
Dung lượng 24,9 KB

Nội dung

It may seem really obvious that operators should be used only when appropriate, but for some reason there is a certain "coolness factor" about operators that seems to tempt some people to add them even when their semantics are unclear. There are many scenarios requiring operators, such as when there is a relation between instances of a type, or when creating an arithmetic type. But there are also less clear-cut cases, where one needs to consider the expectations of clients of the class, and where perceived ambiguity might make a member function a better choice. Operators have been plied into unusual service over the years. Concatenating strings with addition operators and I/O with shift operators are two common examples where the operators do not necessarily have a mathematical meaning, but have been used for other semantic purposes. Some have questioned the use of the subscript operator for accessing elements in a std::map. (Others, of course, think it's perfectly natural. And they are right.) Sometimes, using operators for tasks other than their role with built-in types makes sense. Other times, it can go horribly wrong, causing confusion and ambiguities. When you choose to overload operators with meanings that deviate from those of the built-in types, you must do so carefully. You must ensure that the meaning is obvious and that the precedence is correct. That was the reason for choosing the shift operators for I/O in the IOStreams library. The operators clearly suggested moving something one direction or the other and the precedence of the shift operators put them lower than most others. If you create a class representing a car, some might find operator-= convenient. However, what might that operator mean to clients? Some might think it was used to account for gasoline used while driving. Others might think that it was used to account for depreciation of the car's value (an accountant, of course). Adding that operator is wrongheaded because it doesn't have a clear purpose, whereas a member function can name the operation providing clarity. Don't add operators just because it makes for "cool" coding. Add them because it makes sense, be sure to add all the operators that apply, and be sure to use the Boost.Operators library! Understanding How It Works We'll now take a look at how this library works, to further your understanding of how to use it properly. For Boost.Operators, it's not a hard thing to do. Let's see how to implement support for less_than_comparable. You need to know the type for which you'll add support, and you need to augment that type with operators that use one or more operators supplied for that type. less_than_comparable requires that we provide operator<, operator>, operator<=, and operator>=. Of these, by now, you know how to implement operator>, operator<=, and operator>= in terms of operator<. Here's how one might implement it. template <class T> class less_than1 { public: friend bool operator>(const T& lhs,const T& rhs) { return rhs<lhs; } friend bool operator<=(const T& lhs,const T& rhs) { return !(rhs<lhs); } friend bool operator>=(const T& lhs,const T& rhs) { return !(lhs<rhs); } }; For operator>, you just need to switch the order of the arguments. For operator<=, observe that a<=b means that b is not less than a. Thus, the implementation is to call operator< with the arguments in reverse order and negate the result. For operator>=, there's the similar observation that a>=b also means that a is not less than b. Thus, the implementation just negates the result of calling operator<. This is a working example: You could use it directly and it would do the right thing. However, it would be nice to also have a version that supports comparisons between T and compatible types, which is simply a case of adding more overloads. For symmetry, you need to allow either type to be on the left side of the operation. (This is easy to forget when adding operators manually; one tends to only see clearly the fact that the right side must accept the other type. Of course, your two-type version of less_than wouldn't make such silly mistakes, right?) template <class T,class U> class less_than2 { public: friend bool operator<=(const T& lhs,const U& rhs) { return !(lhs>rhs); } friend bool operator>=(const T& lhs,const U& rhs) { return !(lhs<rhs); } friend bool operator>(const U& lhs,const T& rhs) { return rhs<lhs; } friend bool operator<(const U& lhs,const T& rhs) { return rhs>lhs; } friend bool operator<=(const U& lhs,const T& rhs) { return !(rhs<lhs); } friend bool operator>=(const U& lhs,const T& rhs) { return !(rhs>lhs); } }; There it is! Two fully functioning less_than classes. Of course, to match the functionality of less_than_comparable in the Operators library, we must somehow get rid of the suffix stating how many types are used. What we really want is one version, or at least one name. If you are working with a compiler that supports partial template specialization, you're in luck, because it's basically a three-liner to make this happen. But, there are still a number of programmers who don't have that luxury, so we'll do it the hard way, and avoid partial specialization altogether. First, we know that we need something called less_than, which is to be a template accepting one or two argument types. We also know that the second type should be optional, which we can accomplish by adding a default type that we know users won't pass to the template. struct dummy {}; template <typename T,typename U=dummy> class less_than {}; We need some mechanism for selecting the correct version of less_than (less_than1 or less_than2); we can do this without partial template specialization by using an auxiliary class that is parameterized on one type, with a nested template struct that accepts an additional type. Then, using full specialization, we can make sure that whenever the type U is dummy, less_than1 is selected. template <typename T> struct selector { template <typename U> struct type { typedef less_than_2<U,T> value; }; }; The preceding version creates a type definition called value, which is a correct instantiation of the less_than2 template that we've created. template<> struct selector<dummy> { template <typename U> struct type { typedef less_than1<U> value; }; }; The fully specialized selector creates a typedef for the other version, less_than1. To make it easier for the compiler, we'll create another auxiliary class with the sole responsibility of collecting the correct type and storing it in the suitably named typedef type. template <typename T,typename U> struct select_implementation { typedef typename selector<U>::template type<T>::value type; }; The syntax is not so pleasing to the eye, because of the nested parameterized struct in the selector class, but as clients of this class don't have to read this part of the code, that's really not a big issue. Now that we have all the ingredients that we need to select a correct implementation, we finalize the class by deriving less_than from select_implementation<T,U>::type, which evaluates to either less_than1 or less_than2, depending on whether the user has supplied one or two types to our class. template <typename T,typename U=dummy> class less_than : select_implementation<T,U>::type {}; That's it! We now have a fully working version of less_than, which users can use in the easiest possible way due to the extra effort we spent in adding a mechanism for detecting and selecting the correct version of the implementation. We also know exactly how operator< can be used to create the remaining operators that are applicable for any type that is less_than_comparable. Doing the same for the other operators is just a matter of being meticulous and understanding how different operators work together to form new concepts. The Things That Remain We haven't yet spoken about the remaining part of the Operators library, the iterator helpers. I won't show example code for those, because you'll mainly want to use them when defining iterator types, and that needs additional explanation that does not fit in this chapter or in this book. However, I mention them here because if you are defining iterator types without the help of Boost.Iterators, you most definitely want to use these helpers. The dereference operators help define the correct operators regardless of whether you are using a proxy class. They are also useful when defining smart pointers, which typically also require defining both operator-> and operator*. The iterator helpers group together concepts that are required for the different types of iterators. For example, a random access iterator needs to be bidirectional_iterable, totally_ordered, additive, and indexable. When defining new iterator types, which should preferably be done with the help of the Boost.Iterator library, the Operators library can help. Operators Summary Providing the correct set of relational and arithmetic operators for user-defined classes is vital and provides significant challenges to get right. With the use of the Operators library, this task is greatly simplified, and correctness and symmetry come almost for free. In addition to the help that the library offers in defining the full sets of operators, the naming and definitions of the concepts that a class can support is made explicit in the definition of the class (and by the Operators library!). In this chapter, we have seen several examples of how using this library improves programming with operators by sim plification and ensured correctness. It is a sad fact that providing important relational and arithmetic operators for user- defined types is often overlooked, and part of the reason is that there is so much work involved to get it right. This is no longer the case, and Boost.Operators is the reason why. An important consideration when providing relational and arithmetic operators is to make sure that they are warranted in the first place. When there is an ordering relation between types, or for numeric types, this is always the case, but for other types of classes, operators may not convey intent clearly. Operators are almost always syntactic sugar, and the importance of syntactic sugar must never be underestimated. Unfortunately, operators are also seductive. Use them wisely, for they wield vast power. When you choose to add operators to a class, the Boost.Operators library increases the quality and efficiency of your work. The conclusion is that you should augment your classes with operators only after careful thought, and use the Operators library whenever you get the chance! The Operators library is the result of contributions from several people. It was started by David Abrahams, and has since received valuable additions from Jeremy Siek, Aleksey Gurtovoy, Beman Dawes, and Daryle Walker. As is the case for most Boost libraries, innumerable other people have been involved in making this library what it is today. . how to use it properly. For Boost.Operators, it's not a hard thing to do. Let's see how to implement support for less_than_comparable. You need to know the type for which you'll. applicable for any type that is less_than_comparable. Doing the same for the other operators is just a matter of being meticulous and understanding how different operators work together to form new. mean to clients? Some might think it was used to account for gasoline used while driving. Others might think that it was used to account for depreciation of the car's value (an accountant,

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