EXPLORATION 42

image

Function Objects

Classes have many, many uses in C++ programs. This Exploration introduces one powerful use of classes to replace functions. This style of programming is especially useful with the standard algorithms.

The Function Call Operator

The first step is to take a look at an unusual “operator,” the function call operator, which lets an object behave as a function. Overload this operator the same way you would any other. Its name is operator(). It takes any number of parameters and can have any return type. Listing 42-1 shows another iteration of the generate_id class (last seen in Listing 41-5),this time replacing the next() member function with the function call operator. In this case, the function has no parameters, so the first set of empty parentheses is the operator name, and the second set is the empty parameter list.

Listing 42-1.  Rewriting generate_id to Use the Function Call Operator

#ifndef GENERATE_ID_HPP_
#define GENERATE_ID_HPP_
 
/// Class for generating a unique ID number.
class generate_id
{
public:
  generate_id() : counter_{0} {}
  long operator()();
private:
  short counter_;
  static short prefix_;
  static short const max_counter_{32767};
};
 
#endif

Listing 42-2 displays the implementation of the function call operator (and prefix_, which also requires a definition).

Listing 42-2.  Implementation of the generate_id Function Call Operator

#include "generate_id.hpp"
 
short generate_id::prefix_{1};
 
long generate_id::operator()()
{
  if (counter_ == max_counter_)
    counter_ = 0;
  else
    ++counter_;
  return static_cast<long>(prefix_) * (max_counter_ + 1) + counter_;
}

In order to use the function call operator, you must first declare an object of the class type, then use the object name as though it were a function name. Pass arguments to this object the way you would to an ordinary function. The compiler sees the use of the object name as a function and invokes the function call operator. Listing 42-3 shows a sample program that uses a generate_id function call operator to generate ID codes for new library works. (Remember the work class from Exploration 38? Collect work and its derived classes into a single file, add the necessary #include directives, and include guard. Call the resulting file library.hpp. Or download a complete library.hpp from the book’s web site.) Assume that int_to_id converts an integer identification into the string format that work requires and that accession adds a work-derived object to the library’s database.

Listing 42-3.  Using a generate_id Object’s Function Call Operator

#include <iostream>
 
#include "generate_id.hpp"
#include "library.hpp"
 
bool get_movie(std::string& title, int& runtime)
{
  std::cout << "Movie title: ";
  if (not std::getline(std::cin, title))
    return false;
  std::cout << "Runtime (minutes): ";
  if (not (std::cin >> runtime))
    return false;
  return true;
}
 
int main()
{
  generate_id gen{};           // Create an ID generator
  std::string title{};
  int runtime{};
  while (get_movie(title, runtime))
  {
    movie m(int_to_id(gen()), title, runtime);
    accession(m);
  }
}

Function Objects

A function object or functor is an object of class type for a class that overloads the function call operator. Informally, programmers sometimes also speak of the class as a “function object,” with the understanding that the actual function objects are the variables defined with that class type.

C++ 03 programs often used functors, but C++ 11 has lambdas, which are much easier to read and write. (Recall lambdas from Exploration 23?) So what do functors offer that lambdas lack? To answer that question, consider the following problem.

Suppose you need a vector that contains integers of increasing value. For example, a vector of size 10 would contain the values 1, 2, 3, . . . , 8, 9, 10. The std::generate algorithm takes an iterator range and calls a function or functor for each element of the range, assigning the result of the functor to successive elements. Write a lambda to use as the final argument to std::generate. (Construct a vector of a specific size by passing the desired size to a constructor. Remember to use parentheses instead of curly braces to call the right constructor.) Compare your solution with mine in Listing 42-4.

Listing 42-4.  The Main Program for Generating Successive Integers

#include <algorithm>
#include <vector>
 
int main()
{
  std::vector<int> vec(10);
  int state;
  std::generate(vec.begin(), vec.end(), [&state]() { return ++state; });
}

Okay, that was easy, but the solution is not very general. The lambda cannot be reused anywhere else. It needs the state variable, and you can have only one such lambda per state variable. Can you think of a way to write a lambda such that you can have more than one generator, each with its own state? That’s much harder to do with lambdas, but easy with functors. Write a functor class to generate successive integers, so the functor can be used with the generate algorithm. Name the class sequence. The constructor takes two arguments: the first specifies the initial value of the sequence, and the second is the increment. Each time you call the function call operator, it returns the generator value, then increments that value, which will be the value returned on the next invocation of the function call operator. Listing 42-5 shows the main program. Write your solution in a separate file, sequence.hpp, using only inline functions (so you don’t have to compile a separate sequence.cpp source file).

Listing 42-5.  The Main Program for Generating Successive Integers

#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>
 
#include "sequence.hpp"
 
int main()
{
  int size{};
  std::cout << "How many integers do you want? ";
  std::cin >> size;
  int first{};
  std::cout << "What is the first integer? ";
  std::cin >> first;
  int step{};
  std::cout << "What is the interval between successive integers? ";
  std::cin >> step;
  
  std::vector<int> data(size);
  // Generate the integers to fill the vector.
  std::generate(data.begin(), data.end(), sequence(first, step));
 
  // Print the resulting integers, one per line.
  std::copy(data.begin(), data.end(),
            std::ostream_iterator<int>(std::cout, " "));
}

Compare your solution with mine, shown in Listing 42-6.

Listing 42-6.  The sequence.hpp File

#ifndef SEQUENCE_HPP_
#define SEQUENCE_HPP_
 
/// Generate a sequence of integers.
class sequence
{
public:
  /// Construct the functor.
  /// @param start the first value the generator returns
  /// @param step increment the value by this much for each call
  sequence(int start, int step ) : value_{start}, step_{step} {}
  sequence(int start) : sequence{start, 1} {}
  sequence() : sequence{0} {}
 
  /// Return the current value in the sequence, and increment the value.
  int operator()()
  {
    int result(value_);
    value_ = value_ + step_;
    return result;
  }
private:
  int value_;
  int const step_;
};
 
#endif

The generate algorithm has a partner, generate_n, which specifies an input range with an iterator for the start of the range and an integer for the size of the range. The next Exploration examines this and several other useful algorithms.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset