Things to think about when designing your programs. | ||
---|---|---|
Prev | Chapter 4. Using the Standard Template Library | Next |
A lot of STL classes and functions take a function class as a template argument. It's sort of hard to explain what this means, so I'll start with an example that comes up a lot. Suppose you want to sort an array of strings but you want to be able to decide at run time what order to use. For example, your program may take command line arguments that make it do plain alphabetical order or reverse alphabetical order. The super-slick OO way to do this is to use the Command design pattern:
Example 4-2. Sorting with the Command Design Pattern
template<class Type> class Comparison { public: virtual ~Comparison() {}; virtual int Compare( const Type & x, const Type & y ) = 0; }; class AlphaCompare : public Comparison<string> { ... }; class ReverseAlphaCompare : public Comparison<string> { ... }; |
Example 4-3. Sorting with Commands
void DoSort( vector<string> & v, Comparison<string> * c ) { // use c->Compare(v[i], v[j]) to sort the vector ... } |
Example 4-4. Using the command line
Comparison<string> * c; if( alpha command line flag ) { c = new AlphaCompare(); } else if( reverse alpha command line flag ) { c = new ReverseAlphaCompare(); } ... DoSort( someVector, c ); |
Example 4-5. STL sort function
template<class RanIt> void sort(RanIt first, RanIt last); template<class RanIt, class Pred> void sort(RanIt first, RanIt last, Pred pr); |
Example 4-6. Predicate class
class SomePredicate { public: bool operator() ( const Type & a, const Type & b ); }; |
The code for sort actually looks something like this:
Example 4-7. How sort uses predicates
template<class RanIt, class Pred> void sort(RanIt first, RanIt last, Pred pr) { ... // do some efficient sort, and when we need to compare two items: if( pr( item1, item2 ) ) // calls operator() function { // put item1 first } else { // put item2 first } ... } |
Example 4-8. Changing sort order at run-time
Predicate pr; if( alpha flag ) { pr = some instance of class Predicate that sorts in alphabetical order; } else if( reverse alpha flag ) { pr = some other instance of class Predicate that sorts in reverse order; } ... sort( someVector.begin(), someVector.begin.end(), pr ); |
There are several things to note about this code.
We have to pass in the predicate object by value. This means that in the above example, pr is copied, and it is the copy that the sort function deals with.
The variable pr is declared to be of type Predicate, so we always end up calling this version of sort: sort( vector::iterator, vector::iterator, Predicate ).
One solution that many people came up with when they ran into this problem is the following:
Example 4-9. Wrong solution: virtual operator()
class FooComparison { public: virtual bool operator()( const string & a, const string & b ) = 0; }; class AlphaFooComparison : class FooComparison { ... }; class ReverseAlphaFooComparison : class FooComparison { ... }; main() { FooComparison * pr; if( alpha flag ) { pr = new AlphaFooComparison(); } else if( reverse alpha flag ) { pr = new ReverseAlphaFooComparison(); } ... sort( someVector.begin(), someVector.end(), *pr ); } |
Almost. If you fill in the details of the code and compile and run it, your program dies and says "Pure virtual function called."
The problem is this: The magic of virtual functions only works when you invoke them with a pointer or a reference:
Example 4-10. Virtual function magic
FooComparison * a; FooComparison & b; a->operator()( x, y ); // virtual function magic! (*a)( x, y ); // different syntax, same magic! b( x, y ); // magic! FooComparsion c; c( x, y ); // NO MAGIC! |
Example 4-11. Where sorting goes wrong
sort( vector<string>::iterator first, vector<string>::iterator last, FooComparsion pr ) { ... // sorting right along... if( pr( item1, item2 ) ) // OOPS! { ... } ... } |
So, what do we do? Well, things would be okay if we could pass in that predicate by reference. So, we make another brave attempt and explicitly give sort its template arguments:
Example 4-12. Trying to be more explicit (and failing)
sort<vector<string>::iterator,FooComparsion&>( someVector.begin(), someVector.end(), pr ); |
How about using an Adapter class: The idea here is that we must pass in something that you can call operator() on, that you can pass around by value (by copy), and that will do virtual function magic. The operator() part means it must be an instance of class, but how do we do the rest of it? It sounds like we need to put a wrapper around a pointer somehow:
Example 4-13. STLTestAdapter class
template<class Type> class Comparison { public: virtual ~Comparison() {}; virtual bool operator()( const Type & x, const Type & y ) = 0; }; template<class Type> class STLTestAdapter { public: STLTestAdapter( Comparison<Type> * c ) : myCompare(c) {} bool operator()( const Type & left, const Type & right ) { return (*myCompare)( left, right ); } private: Comparison<Type> * myCompare; }; ... main() { Comparison * pr; ... // our sorting call becomes: STLTestAdapter<string> adapter( pr ); sort( someVector.begin(), someVector.end(), adapter ); ... } |
Example 4-14. How sort uses predicates
sort( vector<string>::iterator first, vector<string>::iterator last, STLTestAdapter<string> pr ) { ... if( pr( item1, item2 ) ) { ... } ... } |
Example 4-15. What the parentheses do
(pr.myCompare)->operator()( item1, item2 ) |
Here's a full source listing that demonstrates sorting that works like we want, just for reference:
Example 4-16. Full source of sorting program
#include <algorithm> #include <string> #include <vector> #include <iostream> template<class Type> class Comparison { public: virtual ~Comparison() {}; virtual bool operator()( const Type & x, const Type & y ) = 0; }; template<class Type> class STLTestAdapter { public: STLTestAdapter( Comparison<Type> * c ) : myCompare(c) {} bool operator()( const Type & left, const Type & right ) { return (*myCompare)( left, right ); } private: Comparison<Type> * myCompare; }; class AlphaOrder : public Comparison<string> { public: bool operator()( const string & left, const string & right ) { return left < right; } }; class ReverseAlphaOrder : public Comparison<string> { public: bool operator()( const string & left, const string & right ) { return right < left; } }; void Print( vector<string> & v ) { vector<string>::iterator iter; for( iter = v.begin(); iter != v.end(); iter++ ) { cout << *iter << endl; } cout << "-----" << endl; } void DoSort( vector<string> & v, Comparison<string> * compare ) { STLTestAdapter<string> adapter(compare); sort( v.begin(), v.end(), adapter ); } main() { vector<string> words; words.push_back( "banana" ); words.push_back( "apple" ); words.push_back( "spoon" ); words.push_back( "pancake" ); words.push_back( "ant" ); Print( words ); DoSort( words, new AlphaOrder() ); Print( words ); DoSort( words, new ReverseAlphaOrder() ); Print( words ); } |