- Modern C++:Efficient and Scalable Application Development
- Richard Grimes Marius Bancila
- 755字
- 2021-06-10 18:28:10
Function objects
A function object, or functor, is a custom type that implements the function call operator: (operator()). This means that a function operator can be called in a way that looks like it is a function. Since we haven't covered classes yet, in this section we will just explore the function objects types that are provided by the Standard Library and how to use them.
The <functional> header file contains various types that can be used as function objects. The following table lists these:
These are all binary function classes, other than bit_not, logical_not, and negate, which are unary. Binary function objects act on two values and return a result, unary function objects act on a single value and return a result. For example, you could calculate the modulus of two numbers with this code:
modulus<int> fn;
cout << fn(10, 2) << endl;
This declares a function object called fn that will perform modulus. The object is used in the second line, which calls the operator() function on the object with two parameters, so the following line is equivalent to the preceding line:
cout << fn.operator()(10, 2) << endl;
The result is that the value of 0 is printed on the console. The operator() function merely performs the modulus on the two parameters, in this case 10 % 2. This does not look too exciting. The <algorithm> header contains functions that work on function objects. Most take predicates, that is, logical function objects, but one, transform, takes a function object that performs an action:
// #include <algorithm>
// #include <functional>
vector<int> v1 { 1, 2, 3, 4, 5 };
vector<int> v2(v1.size());
fill(v2.begin(), v2.end(), 2);
vector<int> result(v1.size());
transform(v1.begin(), v1.end(), v2.begin(),
result.begin(), modulus<int>());
for (int i : result)
{
cout << i << ' ';
}
cout << endl;
This code will perform five modulus calculations on the values in the two vectors. Conceptually, it does this:
result = v1 % v2;
That is, each item in result is the modulus of the corresponding item in v1 and v2. In the code, the first line creates a vector with the five values. We will calculate the modulus of these values with 2, so the second line declares an empty vector but with the same capacity as the first vector. This second vector is filled by calling the fill function. The first parameter is the address of the first item in the vector and the end function returns the address after the last item in the vector. The final item in the function call is the value that will be placed in the vector in every item starting with the item pointed to by the first parameter up to, but excluding, the item pointed to by the second parameter.
At this point, the second vector will contain five items and each one will be 2. Next, a vector is created for the results; and again, it is the same size as the first array. Finally, the calculation is performed by the transform function, shown here again:
transform(v1.begin(), v1.end(),
v2.begin(), result.begin(), modulus<int>());
The first two parameters give the iterators of the first vector and from this the number of items can be calculated. Since all three vectors are the same size, you only need the begin iterator for v2 and result.
The last parameter is the function object. This is a temporary object and only exists during this statement; it has no name. The syntax used here is an explicit call to the constructor of the class; it is templated so you need to give the template parameter. The transform function will call the operator(int,int) function on this function object for each item in v1 as the first parameter and the corresponding item in v2 as the second parameter and it will store the result in the corresponding position in result.
Since transform takes any binary function object as the second parameter, you can pass an instance of plus<int> to add a value of 2 to every item in v1, or pass an instance of multiplies<int> to multiply every item in v1 by 2.
One situation where function objects are useful is when performing multiple comparisons using a predicate. A predicate is a function object that compares values and returns a Boolean. The <functional> header contains several classes to allow you to compare items. Let's see how many items in the result container are zero. To do this, we use the count_if function. This will iterate over a container, apply the predicate to every item, and count how many times the predicate returns a value of true. There are several ways to do this. The first defines a predicate function:
bool equals_zero(int a)
{
return (a == 0);
}
A pointer to this can then be passed to the count_if function:
int zeros = count_if(
result.begin(), result.end(), equals_zero);
The first two parameters indicate the range of values to check. The last parameter is a pointer to the function that is used as the predicate. Of course, if you are checking for different values you can make this more generic:
template<typename T, T value>
inline bool equals(T a)
{
return a == value;
}
Call it like this:
int zeros = count_if(
result.begin(), result.end(), equals<int, 0>);
The problem with this code is that we are defining the operation in a place other than where it is used. The equals function could be defined in another file; however, with a predicate it is more readable to have the code that does the checking defined close to the code that needs the predicate.
The <functional> header also defines classes that can be used as function objects. For example, equal_to<int>, which compares two values. However, the count_if function expects a unary function object, to which it will pass a single value (see the equals_zero function, described previously). equal_to<int> is a binary function object, comparing two values. We need to provide the second operand and to do this we use the helper function called bind2nd:
int zeros = count_if(
result.begin(), result.end(), bind2nd(equal_to<int>(), 0));
The bind2nd will bind the parameter 0 to the function object created from equal_to<int>. Using a function object like this brings the definition of the predicate closer to the function call that will use it, but the syntax looks rather messy. C++11 provides a mechanism to get the compiler to determine the function objects that are required and bind parameters to them. These are called lambda expressions.