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

6 300 0
O''''Reilly Network For Information About''''s Book part 55 docx

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

Thông tin tài liệu

defines operator==. It implements this operator in a generic fashion by using operator< for the parameterizing type. Then, the class some_class, wishing to utilize the services of equivalent, derives from it and passes itself as equivalent's template parameter. Therefore, the resulting operator== is defined for the type some_class, implemented in terms of some_class's operator<. That's all there is to the Barton-Nackmann trick. This is a simple yet immensely useful pattern, quite beautiful in its elegance. Strict Weak Ordering I have already mentioned strict weak orderings twice in this book, and if you're not familiar with what they are, this brief digression should help. A strict weak ordering is a relation between two objects. First, let's get a bit theoretical and then we can make it more concrete. For a function f(a,b) that implements a strict weak ordering, with a and b being two objects of the same type, we say that a and b are equivalent if f(a,b) is false and f(b,a) is false. This means that a does not precede b, and b does not precede a. We can thus consider them to be equivalent. Furthermore, f(a,a) must always yield false [5] and if f(a,b) is true, then f(b,a) must be false. [6] Also, if f(a,b) and f(b,c) is true, then so is f(a,c). [7] Finally, if f(a,b) is false and f(b,a) is false, and if f(b,c) is false and f(c,b) is false, then f(a,c) is false and f(c,a) is false. [8] [5] This is irreflexivity. [6] This is antisymmetry. [7] This is transitivity. [8] This is transitivity of equivalence. Applying the preceding to our previous example (with the class thing) can help clarify the theory. The less than comparison for things is implemented in terms of less than for std::string. This, in turn, is a lexicographical comparison. So, given a thing a containing the string "First," a thing b containing the string "Second," and a thing c containing the string "Third," let's assert the earlier definitions and axioms. #include <cassert> #include <string> #include "boost/operators.hpp" // Definition of class thing omitted int main() { thing a("First"); thing b("Second"); thing c("Third"); // assert that a<b<c assert(a<b && a<c && !(b<a) && b<c && !(c<a) && !(c<b)); // Equivalence thing x=a; assert(!(x<a) && !(a<x)); // Irreflexivity assert(!(a<a)); // Antisymmetry assert((a<b)==!(b<a)); // Transitivity assert(a<b && b<c && a<c); // Transitivity of equivalence thing y=x; assert( (!(x<a) && !(a<x)) && (!(y<x) && !(x<y)) && (!(y<a) && !(a<y))); } Now, all of these asserts hold, because std::string implements a strict weak ordering. [9] Just as operator< should define a strict weak ordering, so should operator>. Later on, we'll look at a very concrete example of what happens when we fail to acknowledge the difference between equivalence (which is required for a strict weak ordering) and equality (which is not). [9] In fact, std::string defines a total ordering, which is a strict weak ordering with the additional requirement that equivalence and equality are identical. Avoid Object Bloating In the previous example, our class derived from two base classes: less_than_comparable<thing> and equivalent<thing>. Depending on your compiler, you may pay a price for this multiple inheritance; thing may be much larger than it needs to be. The standard permits a compiler to use the empty base optimization to make a base class that contains no data members, no virtual functions, and no duplicated base classes, to take zero space in derived class objects, and most modern compilers perform that optimization. Unfortunately, using the Operators library often leads to inheriting from multiple classes and few compilers apply the empty base optimization in that case. To avoid the potential object size bloating, Operators supports a technique known as base class chaining. Every operator class accepts an optional, additional template parameter, from which it derives. By having one concept class derive from another, which derives from another, which derives from another…(you get the idea), the multiple inheritance is eliminated. This alternative is easy to use. Rather than inheriting from several base classes, simply chain the classes together, like so. // Before boost::less_than_comparable<thing>,boost::equivalent<thing> // After boost::less_than_comparable<thing,boost::equivalent<thing> > This method removes the inheritance from multiple empty base classes, which may not trigger your compiler's empty base optimization, in favor of derivation from a chain of empty base classes, increasing the chance of triggering the empty base optimization and reducing the size of the derived classes. Experiment with your compiler to see what benefits you can gain from this technique. Note that there is a limit to the length of the base class chain that depends upon the compiler. There's also a limit to the length of the chain a human can grok! That means that classes that need to derive from many operator classes may need to group them. Better yet, use the composite concepts already provided by the Operators library. The difference in size between using base class chaining and multiple inheritance on a popular compiler [10] that doesn't perform the empty base class optimization for multiple inheritance is quite large for my tests. Using base class chaining ensures that the size of types is not negatively affected, whereas with multiple inheritance, the size grows by 8 bytes for a trivial type (admittedly, 8 additional bytes isn't typically a problem for most applications). If the size of the wrapped type is very small, the overhead caused by multiple inheritance is potentially more than is tolerable. Because it is so easy, consider using base class chaining all the time! [10] I say this both because there's no need for calling names, and because everyone already knows that I'm talking about Microsoft's old compiler (their new one rocks). Operators and Different Types Sometimes, an operator involves more than one type. For example, consider a string class that supports concatenation from character arrays through operator+ and operator+=. The Operators library helps here too, by way of the two-argument versions of the operator templates. In the case of the string class, there is probably a conversion constructor available that accepts a char*, but as we shall see, that doesn't solve all of the problems for this class. Here's the string class that we'll use. class simple_string { public: simple_string(); explicit simple_string(const char* s); simple_string(const simple_string& s); ~simple_string(); simple_string& operator=(const simple_string& s); simple_string& operator+=(const simple_string& s); simple_string& operator+=(const char* s); friend std::ostream& operator<<(std::ostream& os,const simple_string& s); }; As you can see, we've already added two versions of operator+= for simple_string. One accepts a const simple_string&, and the other accepts a const char*. As is, our class supports usage like this. simple_string s1("Hello there"); simple_string s2(", do you like the concatenation support?"); s1+=s2; s1+=" This works, too"; Although the preceding works as intended, we still haven't provided the binary operator+, an omission that the class' users definitely won't be pleased with. Note that for our simple_string, we could have opted to enable concatenation by omitting the explicit conversion constructor. However, doing so would involve an extra (unnecessary) copy of the character buffer, and the only savings would be the omission of an operator. // This won't compile simple_string s3=s1+s2; simple_string s4=s3+" Why does this class behave so strangely?"; Now let's use the Operators library to supply the missing operators for the class. Note that there are actually three missing operators. simple_string operator+(const simple_string&,const simple_string&); simple_string operator+(const simple_string& lhs, const char* rhs); simple_string operator+(const char* lhs, const simple_string& rhs); When defining operators manually, it's easy to forget one of the overloads for taking one const simple_string& and one const char*. When using the Operators library, you can't forget, because the library is implementing the missing operators for you! What we want for simple_string is the addable concept, so we simply derive simple_string from boost::addable<simple_string>. class simple_string : boost::addable<simple_string> { In this case, however, we also want the operators that allow mixing simple_strings and const char*s. To do this, we must specify two typesthe result type, simple_string, and the second argument type, const char*. We'll utilize base class chaining to avoid increasing the size of the class. class simple_string : boost::addable<simple_string, boost::addable2<simple_string,const char*> > { This is all that's needed for supporting the full set of operators that we aimed for! As you can see, we used a different operator class: addable2. If you're using a compiler that supports partial template specialization, you don't have to qualify the name; use addable instead of addable2. There are also versions of the classes with the suffix "1" provided for symmetry. It may increase the readability to always be explicit about the number of arguments, which gives us the following derivation for simple_string. class simple_string : boost::addable1<simple_string, boost::addable2<simple_string,const char*> > { Choose between them according to taste, and if your compiler supports partial template specialization, the simplest choice is to omit the suffixes altogether. class simple_string : . inheritance on a popular compiler [10] that doesn't perform the empty base class optimization for multiple inheritance is quite large for my tests. Using base class chaining ensures that the. manually, it's easy to forget one of the overloads for taking one const simple_string& and one const char*. When using the Operators library, you can't forget, because the library. that's needed for supporting the full set of operators that we aimed for! As you can see, we used a different operator class: addable2. If you're using a compiler that supports partial template

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

Từ khóa liên quan

Tài liệu cùng người dùng

  • Đang cập nhật ...

Tài liệu liên quan