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

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

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 (22.55 KB, 6 trang )

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 :

×