Noeud:Function Objects - in a Nutshell, Noeud « Next »:Some Predefined Function Objects, Noeud « Up »:Generic Algorithms and Function Objects
If you are familiar with the concept of a function pointer, then function objects aren't too disimilar. The basic concept holds for both: we can create many different instances of the same function, but each operating with a different state. However, the difference is that a function object is not a function - it is an object that behaves like a function.
We'll begin by describing some predefined function objects and look at how they work, and afterwards look at how to create our own function objects. This will all tie in with the next section, Introducing Generic Algorithms, which will show you how to exploit some of the function objects we'll look at in this chapter - many generic algorithms accept a function object as one of their arguments.
The STL provides a number predefined function objects. Our purpose here is to describe a few of them; the rest will be provided in the Some Predefined Function Objects.
Right. So we said that a function object is a class that behaves like a
function. But it isn't actually a function; we're just wrapping up
functional behaviour in a class by overloading the function call
operator()
so that we can call the class like we'd call a
function. Let's look at a commonly used function, negate
.
negate
returns the negative value of what was passed in. Pretty simple
really; let's look at the declaration in stl_function.h
(don't despair
at it - we'll explain it in due course!)1:
template <class T> struct negate : public unary_function<T, T> { T operator()(const T& x) const { return -x; } };
Note that negate
inherits from unary_function
- in other words,
it takes one parameter. Likewise, a binary function (which we'll meet soon)
takes two parameters. negate
deals with one type, T
, the type
we'll be passing in. This could be anything; an int
, string
, an
Address
object, and so on, with the following restriction: we must be
able to negate the value of what we pass in (so for example we'd expect to get
-5 if we passed in 5 as an int
). We use the overloaded function
operator operator()
to be able to call negate
as a
function. Obviously, when we pass in x
, we return -x
.
Let's see negate
in use with a vector of integers, defining our own
method named with_each
to be able to walk through the elements of the
container:
/* function1.cc * Compiled using g++ function1.cc -o function1 */ #include <vector> #include <functional> /* Define our own method that uses a unary function 'fn' on * a range of elements: */ template <class InputIterator, class Function> void with_each(InputIterator beg, InputIterator end, Function fn) { for( ; beg != end; ++beg) cout << fn(*beg) << endl; } int main() { std::vector<int> v; v.push_back(10); v.push_back(2); v.push_back(4); with_each(v.begin(), v.end(), negate<int>()); return 0; }
Example 3.15: function1.cc
Here's the output:
$ ./function1 -10 -2 -4
The key to understanding what is going on involves looking at the
with_each
algorithm. Notice to start with that the third parameter of
with_each
in main
takes negate<int>()
as it's
argument. This creates an instance of the negate
function. The body of
with_each
works as follows:
for( ; beg != end; ++beg) cout << fn(*beg) << endl;
Here, fn
is obviously negate
, operating on the range of elements
beg
to (but not including2),
end
. It takes *beg
as it's argument, which is a pointer to an
integer. So all we're doing is saying "apply fn
with argument
*beg
" - or, more precisely - "apply negate
with argument
*beg
". More generally, with_each
takes a function object,
fn
, so we could pass any unary function object into with_each
.
negate
is a unary function object. What about binary function objects?
Well, there's not that much difference except that they take two arguments
instead of one, surprise surprise. Let's look at a function object that takes
two arguments, called greater
. greater
takes elements x
and y
and returns true if x
is greater than y
, false
otherwise. It's definition is simple, and isn't too disimilar to
negate
:
template <class T> struct greater : public binary_function<T, T, bool> { bool operator()(const T& x, const T& y) const { return x > y;} };
We're going to hook greater
into an example similar from earlier, using
greater
as an argument to a modified with_each
method:
/* function2.cc * Compiled using g++ function2.cc -o function2 */ #include <vector> #include <functional> /* Define our own method that uses a binary function 'fn' on * a range of elements: */ template <class InputIterator, class T, class Function, class Message> void with_each(InputIterator beg, InputIterator end, T val, Function fn, Message msg) { for( ; beg != end; ++beg) if (fn(*beg, val)) cout << *beg << msg << val << endl; } int main() { std::vector<int> v; v.push_back(10); v.push_back(2); v.push_back(14); with_each(v.begin(), v.end(), 7, greater<int>(), " is greater than "); return 0; }
Example 3.16: function2.cc
Again, the output is fairly obvious:
$ ./function2 10 is greater than 7 14 is greater than 7... and isn't really anything to get excited about. We could have even passed in the pre-defined
less
function object, with Message
being " is less than " and the results would again have been obvious. But the
point is that function objects provide great flexibility and power, more so
when we introduce generic algorithms.
This has been a whistle-stop tour to look describe some function objects and
look at the basic principles involved. The idea is simple - create a class
that does what you want it to do, placing the functionality in the
operator()
body. It doesn't need to be a template class - although it's
always advantageous if you can do so. If you're perplexed about why we used
the with_each
method with the function objects, don't worry because
we're actually mimicing a generic algoritm called for_each
, which we'll
encounter soon - you'll see (in Introducing Generic Algorithms) how
we'll use function objects with generic algorithms in a similar manner to how
we just used with_each
.
This may not have seemed much of an in-depth or informative explanation of function objects, but we're really holding back until we look at generic algorithms. All you need to remember is that function objects wrap up functional behaviour in a class, and we can make calls to that class by calling the overloaded operator.
The declaration of
negate
has been modified to make it a little more readable; the exact
text of negate
will vary from implementation to implementation.
Recall from A Crash Course in Iterators that end
points past the last element in a container.