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 mo
Trang 1defines 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>
Trang 2#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
Trang 3empty 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)
Trang 4Operators 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
Trang 5simple_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*> > {
Trang 6Choose 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 :