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

HandBooks Professional Java-C-Scrip-SQL part 57 docx

6 133 0
Tài liệu đã được kiểm tra trùng lặp

Đ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

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

template struct selector { template struct type { typedef less_than_2 value; }; }; The preceding version creates a type definition called value, which is a correct instantiation of

Trang 1

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

Trang 2

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);

Trang 3

}

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

Trang 4

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

Trang 5

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

Trang 6

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

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