Functional arguments for algorithms don't have to be functions. They can be objects that behave as functions. Such an object is called a function object, or functor. Sometimes you can use a function object when an ordinary function won't work. The STL often uses function objects and provides several function objects that are very helpful.
What Are Function Objects?
Function objects are another example of the power of generic programming and the concept of pure abstraction. You could say that anything that behaves like a function is a function. So, if you define an object that behaves as a function, it can be used as a function.
So, what is the behavior of a function? The answer is: A functional behavior is something that you can call by using parentheses and passing arguments. For example:
So, if you want objects to behave this way you have to make it possible to "call" them by using parentheses and passing arguments. Yes, that's possible (there are rarely things that are not possible in C++). All you have to do is define operator () with the appropriate parameter types:
Now you can use objects of this class to behave as a function that you can call:
The call is equivalent to:
The following is a complete example. This is the function object version of a previous example that did the same with an ordinary function:
The class PrintInt defines objects for which you can call operator () with an int argument. The expression
in the statement
creates a temporary object of this class, which is passed to the for_each() algorithm as an argument. The for_each() algorithm is written like this:
for_each() uses the temporary function object op to call op(*act) for each element act. If the third parameter is an ordinary function, it simply calls it with *act as an argument. If the third parameter is a function object, it calls operator () for the function object op with *act as an argument. Thus, in this example program for_each() calls:
You may be wondering what all this is good for. You might even think that function objects look strange, nasty, or nonsensical. It is true that they do complicate code. However, function objects are more than functions, and they have some advantages:
Function objects are "smart functions."Objects that behave like pointers are smart pointers. This is similarly true for objects that behave like functions: They can be "smart functions" because they may have abilities beyond operator (). Function objects may have other member functions and attributes. This means that function objects have a state. In fact, the same function, represented by a function object, may have different states at the same time. This is not possible for ordinary functions. Another advantage of function objects is that you can initialize them at runtime before you use/call them.
Each function object has its own type.Ordinary functions have different types only when their signatures differ. However, function objects can have different types even when their signatures are the same. In fact, each functional behavior defined by a function object has its own type. This is a significant improvement for generic programming using templates because you can pass functional behavior as a template parameter. It enables containers of different types to use the same kind of function object as a sorting criterion. This ensures that you don't assign, combine, or compare collections that have different sorting criteria. You can even design hierarchies of function objects so that you can, for example, have different, special kinds of one general criterion.
Function objects are usually faster than ordinary functions.The concept of templates usually allows better optimization because more details are defined at compile time. Thus, passing function objects instead of ordinary functions often results in better performance.
In the rest of this subsection I present some examples that demonstrate how function objects can be "smarter" than ordinary functions. In particular, it shows how to benefit from the ability to pass functional behavior as a template parameter.
Suppose you want to add a certain value to all elements of a collection. If you know the value you want to add at compile time, you could use an ordinary function:
If you need different values that are known at compile time, you could use a template instead:
If you process the value to add at runtime, things get complicated. You must pass the value to the function before the function is called. This normally results in some global variable that is used both by the function that calls the algorithm and by the function that is called by the algorithm to add that value. This is messy style.
If you need such a function twice, with two different values to add, and both values are processed at runtime, you can't achieve this with one ordinary function. You must either pass a tag or you must write two different functions. Did you ever copy the definition of a function because it had a static variable to keep its state and you needed the same function with another state at the same time? This is exactly the same type of problem.
With function objects, you can write a "smarter" function that behaves in the desired way. Because the object may have a state, it can be initialized by the correct value. Here is a complete example:
The first call of for_each() adds 10 to each value:
Here, the expression AddValue(10) creates an object of type AddValue that is initialized with the value 10. The constructor of AddValue stores this value as the member theValue. Inside for_each(), "()" is called for each element of coll. Again, this is a call of operator () for the passed temporary function object of type AddValue. The actual element is passed as an argument. The function object adds its value 10 to each element. The elements then have the following values:
The second call of for_each() uses the same functionality to add the value of the first element to each element. It initializes a temporary function object of type AddValue with the first element of the collection:
The output is then as follows:
By using this technique, two different function objects can solve the problem of having a function with two states at the same time. For example, you could simply declare two function objects and use them independently:
Similarly you can provide additional member functions to query or to change the state of the function object during its lifetime.
Note that for some algorithms the C++ standard library does not specify how often function objects are called for each element, and it might happen that different copies of the function object are passed to the elements. This might have some nasty consequences if you use function objects as predicates.
Predefined Function Objects
The C++ standard library contains several predefined function objects that cover fundamental operations. By using them, you don't have to write your own function objects in several cases. A typical example is a function object used as a sorting criterion. The default sorting criterion for operator < is the predefined sorting criterion less<>. Thus, if you declare
it is expanded to,
From there, it is easy to sort elements in the opposite order,
Similarly, many function objects are provided to specify numeric processing. For example, the following statement negates all elements of a collection:
creates a function object of the predefined template class negate that simply returns the negated element of type int for which it is called. The transform() algorithm uses that operation to transform all elements of the first collection into the second collection. If source and destination are equal (as in this case), the returned negated elements overwrite themselves. Thus, the statement negates each element in the collection.
Similarly, you can process the square of all elements in a collection:
Here, another form of the transform() algorithm combines elements of two collections by using the specified operation, and writes the resulting elements into the third collection. Again, all collections are the same, so each element gets multiplied by itself, and the result overwrites the old value.
By using special function adapters you can combine predefined function objects with other values or use special cases. Here is a complete example:
With the statement
all elements of coll1 are transformed into coll2 (inserting) while multiplying each element by 10. Here, the function adapter bind2nd causes multiply<int> to be called for each element of the source collection as the first argument and the value 10 as the second.
The way bind2nd operates is as follows: transform() is expecting as its fourth argument an operation that takes one argument; namely, the actual element. However, we would like to multiply that argument by ten. So, we have to combine an operation that takes two arguments and the value that always should be used as a second argument to get an operation for one argument. bind2nd does that job. It stores the operation and the second argument as internal values. When the algorithm calls bind2nd with the actual element as the argument, bind2nd calls its operation with the element from the algorithm as the first argument and the internal value as the second argument, and returns the result of the operation.
is used as a criterion to specify the elements that are replaced by 42. bind2nd calls the binary predicate equal_to with value 70 as the second argument, thus defining a unary predicate for the elements of the processed collection.
The last statement is similar because the expression
is used to specify the element that should be removed from the collection. It specifies that all elements that are less than value 50 be removed. The output of the program is as follows:
This kind of programming results in functional composition. What is interesting is that all these function objects are usually declared inline. Thus, you use a function-like notation or abstraction, but you get good performance.
There are other kinds of function objects. For example, some function objects provide the ability to call a member function for each element of a collection:
The function object mem_fun_ref calls a specified member function for the element for which it is called. Thus, for each element of the collection coll, the member function save() of class Person is called. Of course, this works only if the elements have type Person or a type derived from Person.
- Complete Tutorial of C++ Template's
- Standard Template Library Tutorial
- Inter Process Communication Tutorial
- Advance Programming in C & C++
Post a Comment