- Modern C++:Efficient and Scalable Application Development
- Richard Grimes Marius Bancila
- 828字
- 2021-06-10 18:28:16
Defining function classes
A functor is a class that implements the () operator. This means that you can call an object using the same syntax as a function. Consider this:
class factor
{
double f = 1.0;
public:
factor(double d) : f(d) {}
double operator()(double x) const { return f * x; }
};
This code can be called like this:
factor threeTimes(3); // create the functor object
double ten = 10.0;
double d1 = threeTimes(ten); // calls operator(double)
double d2 = threeTimes(d1); // calls operator(double)
This code shows that the functor object not only provides some behavior (in this case, performing an action on the parameter) but it also can have a state. The preceding two lines are called through the operator() method on an object:
double d2 = threeTimes.operator()(d1);
Look at the syntax. The functor object is called as if it is a function declared like this:
double multiply_by_3(double d)
{
return 3 * d;
}
Imagine that you want to pass a pointer to a function--perhaps you are want the function's behavior to be altered by external code. To be able to use either a functor or a method pointer, you need to overload your function:
void print_value(double d, factor& fn);
void print_value(double d, double(*fn)(double));
The first takes a reference to a functor object. The second has a C-type function pointer (to which you can pass a pointer to multiply_by_3) and is quite unreadable. In both cases the fn parameter is called in the same way in the implementation code, but you need to declare two functions because they are different types. Now, consider the magic of function templates:
template<typename Fn>
void print_value(double d, Fn& fn)
{
double ret = fn(d);
cout << ret << endl;
}
This is generic code; the Fn type can be a C function pointer or a functor class, and the compiler will generate the appropriate code.
This is a mere implementation detail, but it does mean that you can write a generic function that can take either a C-like function pointer or a functor object as a parameter. The C++ Standard Library uses this magic, which means that the algorithms it provides can be called either with a global function or a functor, or a lambda expression.
The Standard Library algorithms use three type of functional classes, generators, and unary and binary functions; that is, functions with zero, one or two parameters. In addition, the Standard Library calls a function object (unary or binary) that returns a bool predicate. The documentation will tell you if a predicate, unary, or binary function is needed. Older versions of the Standard Library needed to know the types of the return value and parameters (if any) of the function object to work, and, for this reason, functor classes had to be based upon the standard classes, unary_function and binary_function. In C++11, this requirement has been removed, so there is no requirement to use these classes.
In some cases, you will want to use a binary functor when a unary functor is required. For example, the Standard Library defines the greater class that, when used as a function object, takes two parameters and a bool to determine whether the first parameter is greater than the second one, using the operator> defined by the type of both parameters. This will be used for functions that need a binary functor, and hence the function will compare two values; for example:
template<typename Fn>
int compare_vals(vector<double> d1, vector<double> d2, Fn compare)
{
if (d1.size() > d2.size()) return -1; // error
int c = 0;
for (size_t i = 0; i < d1.size(); ++i)
{
if (compare(d1[i], d2[i])) c++;
}
return c;
}
This takes two collections and compares corresponding items using the functor passed as the last parameter. It can be called like this:
vector<double> d1{ 1.0, 2.0, 3.0, 4.0 };
vector<double> d2{ 1.0, 1.0, 2.0, 5.0 };
int c = compare_vals(d1, d2, greater<double>());
The greater functor class is defined in the <functional> header and compares two numbers using the operator> defined for the type. What if you wanted to compare the items in a container with a fixed value; that is, when the operator()(double, double) method on the functor is called, one parameter always has a fixed value? One option is to define a stateful functor class (as shown previously) so that the fixed value is a member of the functor object. Another way to do this is to fill another vector with the fixed value and continue to compare two vectors (this can get quite expensive for large vectors).
Another way is to reuse the functor class, but to bind a value to one of its parameters. A version of the compare_vals function can be written like this, to take just one vector:
template<typename Fn>
int compare_vals(vector<double> d, Fn compare)
{
int c = 0;
for (size_t i = 0; i < d.size(); ++i)
{
if (compare(d[i]) c++;
}
return c;
}
The code is written to call the functor parameter on just one value because it is assumed that the functor object contains the other value to compare. This is carried out by binding the functor class to the parameter:
using namespace::std::placeholders;
int c = compare_vals(d1, bind(greater<double>(), _1, 2.0));
The bind function is variadic. The first parameter is the functor object and it is followed by the parameters that will be passed to the operator() method of the functor. The compare_vals function is passed a binder object that binds the functor to values. In the compare_vals function, the call to the functor in compare(d[i]) is actually a call to the operator() method of the binder object, and this method forwards the parameter d[i] and the bound value to the operator() method of the functor.
In the call to bind, if an actual value is provided (here, 2.0), then that value is passed to the functor at that position in the call to the functor (here, 2,0 is passed to the second parameter). If a symbol preceded by an underscore is used, then it is a placeholder. There are 20 such symbols (_1 to _20) defined in the std::placeholders namespace. The placeholder means "use the value passed in this position to the binder object operator() method call to the functor call operator() method indicated by the placeholder." Thus, the placeholder in this call means "pass the first parameter from invoking the binder and pass it to the first parameter of the greater functor operator()."
The previous code compares each item in the vector with 2.0 and will keep a count of those that are greater than 2.0. You could invoke it this way:
int c = compare(d1, bind(greater<double>(), 2.0, _1));
The parameter list is swapped, and this means that 2.0 is compared with each item in the vector and the function will keep a count of how many times 2.0 is greater than the item.
The bind function, and placeholders, are new to C++11. In prior versions you could use the bind1st and bind2nd functions to bind a value to either the first or second parameter of the functor.