Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |
Wherever a function-like entity is expected by an algorithm,
you can supply either a pointer to an ordinary function or a function object.
When the algorithm issues a call, if it is through a function pointer, than the
native function-call mechanism is used. If it is through a function object,
then that object s operator( ) member executes. In CopyInts2.cpp,
we passed the raw function gt15( ) as a predicate to remove_copy_if( ).
We also passed pointers to functions returning random numbers to generate( )
and generate_n( ).
You cannot use raw functions with function object adaptors
such as bind2nd( ) because they assume the existence of type
definitions for the argument and result types. Instead of manually converting
your native functions into function objects yourself, the standard library
provides a family of adaptors to do the work for you. The ptr_fun( ) adaptors take a pointer to a function and turn it into a function
object. They are not designed for a function that takes no arguments they must
only be used with unary functions or binary functions.
The following program uses ptr_fun( ) to wrap a
unary function:
//: C06:PtrFun1.cpp
// Using ptr_fun() with a unary function.
#include <algorithm>
#include <cmath>
#include <functional>
#include <iostream>
#include <iterator>
#include <vector>
using namespace std;
int d[] = { 123, 94, 10, 314, 315 };
const int DSZ = sizeof d / sizeof *d;
bool isEven(int x) { return x % 2 == 0; }
int main() {
vector<bool> vb;
transform(d, d + DSZ, back_inserter(vb),
not1(ptr_fun(isEven)));
copy(vb.begin(), vb.end(),
ostream_iterator<bool>(cout, " "));
cout << endl;
// Output: 1 0 0 0 1
} ///:~
We can t simply pass isEven to not1, because not1 needs to know the actual argument type and return type its argument uses. The ptr_fun( )
adaptor deduces those types through template argument deduction. The definition
of the unary version of ptr_fun( ) looks something like this:
template<class Arg, class Result>
pointer_to_unary_function<Arg, Result>
ptr_fun(Result (*fptr)(Arg)) {
return
pointer_to_unary_function<Arg, Result>(fptr);
}
As you can see, this version of ptr_fun( )
deduces the argument and result types from fptr and uses them to
initialize a pointer_to_unary_function object that stores fptr. The function call operator for pointer_to_unary_function just calls fptr, as
you can see by the last line of its code:
template<class Arg, class Result>
class pointer_to_unary_function
: public unary_function<Arg, Result> {
Result (*fptr)(Arg); // Stores the f-ptr
public:
pointer_to_unary_function(Result (*x)(Arg)) : fptr(x)
{}
Result operator()(Arg x) const { return fptr(x); }
};
Since pointer_to_unary_function derives from unary_function, the appropriate type definitions come along for the ride and are available to not1.
There is also a binary version of ptr_fun( ),
which returns a pointer_to_binary_function object (which derives from binary_function) that behaves analogously to the unary case. The following program uses
the binary version of ptr_fun( ) to raise numbers in a sequence to
a power. It also reveals a pitfall when passing overloaded functions to ptr_fun( ).
//: C06:PtrFun2.cpp {-edg}
// Using ptr_fun() for a binary function.
#include <algorithm>
#include <cmath>
#include <functional>
#include <iostream>
#include <iterator>
#include <vector>
using namespace std;
double d[] = { 01.23, 91.370, 56.661,
023.230, 19.959, 1.0, 3.14159 };
const int DSZ = sizeof d / sizeof *d;
int main() {
vector<double> vd;
transform(d, d + DSZ, back_inserter(vd),
bind2nd(ptr_fun<double, double, double>(pow),
2.0));
copy(vd.begin(), vd.end(),
ostream_iterator<double>(cout, "
"));
cout << endl;
} ///:~
The pow( ) function is overloaded in the Standard
C++ header <cmath> for each of the floating-point data types, as
follows:
float pow(float, int); // Efficient int power versions
...
double pow(double, int);
long double pow(long double, int);
float pow(float, float);
double pow(double, double);
long double pow(long double, long double);
Since there are multiple versions of pow( ), the
compiler has no way of knowing which to choose. Here, we have to help the
compiler by using explicit function template specialization, as explained in
the previous chapter.
It s even trickier to convert a member function into a
function object suitable for using with the generic algorithms. As a simple
example, suppose we have the classical shape problem and want to apply the draw( )
member function to each pointer in a container of Shape:
//: C06:MemFun1.cpp
// Applying pointers to member functions.
#include <algorithm>
#include <functional>
#include <iostream>
#include <vector>
#include "../purge.h"
using namespace std;
class Shape {
public:
virtual void draw() = 0;
virtual ~Shape() {}
};
class Circle : public Shape {
public:
virtual void draw() { cout <<
"Circle::Draw()" << endl; }
~Circle() { cout <<
"Circle::~Circle()" << endl; }
};
class Square : public Shape {
public:
virtual void draw() { cout << "Square::Draw()"
<< endl; }
~Square() { cout <<
"Square::~Square()" << endl; }
};
int main() {
vector<Shape*> vs;
vs.push_back(new Circle);
vs.push_back(new Square);
for_each(vs.begin(), vs.end(),
mem_fun(&Shape::draw));
purge(vs);
} ///:~
The for_each( ) algorithm passes each element in a sequence to the function object denoted by its third argument. Here, we want the
function object to wrap one of the member functions of the class itself, and so
the function object s argument becomes the pointer to the object for the
member function call. To produce such a function object, the mem_fun( ) template takes a pointer to a member as its argument.
The mem_fun( ) functions are for producing
function objects that are called using a pointer to the object that the member
function is called for, while mem_fun_ref( ) calls the member function directly for an object. One set of overloads of both mem_fun( )
and mem_fun_ref( ) is for member functions that take zero arguments
and one argument, and this is multiplied by two to handle const vs. non-const
member functions. However, templates and overloading take care of sorting all
that out all you need to remember is when to use mem_fun( ) vs. mem_fun_ref( ).
Suppose you have a container of objects (not pointers), and
you want to call a member function that takes an argument. The argument you
pass should come from a second container of objects. To accomplish this, use
the second overloaded form of the transform( ) algorithm:
//: C06:MemFun2.cpp
// Calling member functions through an object reference.
#include <algorithm>
#include <functional>
#include <iostream>
#include <iterator>
#include <vector>
using namespace std;
class Angle {
int degrees;
public:
Angle(int deg) : degrees(deg) {}
int mul(int times) { return degrees *= times; }
};
int main() {
vector<Angle> va;
for(int i = 0; i < 50; i += 10)
va.push_back(Angle(i));
int x[] = { 1, 2, 3, 4, 5 };
transform(va.begin(), va.end(), x,
ostream_iterator<int>(cout, " "),
mem_fun_ref(&Angle::mul));
cout << endl;
// Output: 0 20 60 120 200
} ///:~
Because the container is holding objects, mem_fun_ref( )
must be used with the pointer-to-member function. This version of transform( )
takes the start and end point of the first range (where the objects live); the
starting point of the second range, which holds the arguments to the member
function; the destination iterator, which in this case is standard output; and
the function object to call for each object. This function object is created
with mem_fun_ref( ) and the desired pointer to member. Notice that
the transform( ) and for_each( ) template functions are
incomplete; transform( ) requires that the function it calls return
a value, and there is no for_each( ) that passes two arguments to
the function it calls. Thus, you cannot call a member function that returns void
and takes an argument using transform( ) or for_each( ).
Most any member function works with mem_fun_ref( ).
You can also use standard library member functions, if your compiler doesn t
add any default arguments beyond the normal arguments specified in the standard. For example,
suppose you d like to read a file and search for blank lines. Your compiler may
allow you to use the string::empty( ) member function like this:
//: C06:FindBlanks.cpp
// Demonstrates mem_fun_ref() with string::empty().
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <fstream>
#include <functional>
#include <string>
#include <vector>
#include "../require.h"
using namespace std;
typedef vector<string>::iterator LSI;
int main(int argc, char* argv[]) {
char* fname = "FindBlanks.cpp";
if(argc > 1) fname = argv[1];
ifstream in(fname);
assure(in, fname);
vector<string> vs;
string s;
while(getline(in, s))
vs.push_back(s);
vector<string> cpy = vs; // For testing
LSI lsi = find_if(vs.begin(), vs.end(),
mem_fun_ref(&string::empty));
while(lsi != vs.end()) {
*lsi = "A BLANK LINE";
lsi = find_if(vs.begin(), vs.end(),
mem_fun_ref(&string::empty));
}
for(size_t i = 0; i < cpy.size(); i++)
if(cpy[i].size() == 0)
assert(vs[i] == "A BLANK LINE");
else
assert(vs[i] != "A BLANK LINE");
} ///:~
This example uses find_if( ) to locate the first
blank line in the given range using mem_fun_ref( ) with string::empty( ).
After the file is opened and read into the vector, the process is
repeated to find every blank line in the file. Each time a blank line is found,
it is replaced with the characters A BLANK LINE. All you have to do to
accomplish this is dereference the iterator to select the current string.
Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |