Function Objects



Item 18. Function Objects

Often you'll need something that behaves like a function pointer, but function pointers tend to be unwieldy, dangerous, and (let's admit it) passé. Often the best approach is to use a function object instead of a function pointer.

A function object, like a smart pointer (see Smart Pointers [42, 145]) is an ordinary class object. Whereas a smart pointer type overloads the -> and * (and possibly ->*) operators to mimic a "pointer on steroids," a function object type overloads the function call operator, (), to create a "function pointer on steroids." Consider the following function object that computes the next element in the well-known Fibonacci series (1, 1, 2, 3, 5, 8, 13, ...) with each call:

class Fib {
  public:
    Fib() : a0_(1), a1_(1) {}
    int operator ();
  private:
    int a0_, a1_;
};
int Fib::operator () {
    int temp = a0_;
    a0_ = a1_;
    a1_ = temp + a0_;
    return temp;
}

A function object is just a regular class object, but you can call its operator () member (or members, if there is more than one) with standard function call syntax.

Fib fib;
//...
cout << "next two in series: " << fib()
     << ' ' << fib() << endl;

The syntax fib() is recognized by the compiler as a member function call to the operator () member of fib, identical in meaning to fib.operator() but presumably easier on the eye. The advantage in this case of using a function object in preference to a function or a pointer to a function is that the state required to compute the next number in the Fibonacci series is stored within the Fib object itself. A function implementation would have to resort to global or local static variables or some other base trickery to retain state between invocations of the function, or the information would have to be passed to the function explicitly. Also note that unlike a function that uses static data, we can have multiple, simultaneous Fib objects whose calculations do not interfere with each other.

int fibonacci () {
    static int a0 = 0, a1 = 1; // problematic...                      
    int temp = a0;
    a0 = a1;
    a1 = temp + a0;
    return temp;
}

It's also possible and common to create the effect of a virtual function pointer by creating a function object hierarchy with a virtual operator (). Consider a numeric integration facility that calculates an approximation of the area under a curve, as shown in Figure.

Numeric integration by summing areas of rectangles (simplified)


Our integration function will iteratively call a function for values between low and high in order to approximate the area under the curve as a sum of the areas of rectangles (or some similar mechanism).

typedef double (*F)( double );
double integrate( F f, double low, double high ) {
    const int numsteps = 8;
    double step = (high-low)/numSteps;
    double area = 0.0;
    while( low < high ) {
        area += f( low ) * step;
        low += step;
    }
    return area;
}

In this version, we pass a pointer to the function over which we want to integrate.

double aFunc( double x ) { ... }
//...
double area = integrate( aFunc, 0.0, 2.71828 );

This works, but it's inflexible because it uses a function pointer to indicate the function to be integrated; it can't handle functions that require state or pointers to member functions. An alternative is to create a function object hierarchy. The base of the hierarchy is a simple interface class that declares a pure virtual operator ().

class Func {
  public:
    virtual ~Func();
    virtual double operator ()( double ) = 0;
};
double integrate( Func &f, double low, double high );

Now integrate is capable of integrating any type of function object that is-a Func (see Polymorphism [2, 3]). It's also interesting to note that the body of integrate does not have to change at all (though it does require recompilation), because we use the same syntax to call a function object as we do for a pointer to function. For example, we can derive a type of Func that can handle non-member functions:

class NMFunc : public Func {
  public:
    NMFunc( double (*f)( double ) ) : f_(f) {}
    double operator ()( double d ) { return f_( d ); }
  private:
    double (*f_)( double );
};

This allows us to integrate all the functions of our original version:

double aFunc( double x ) { ... }
//...
NMFunc g( aFunc );
double area = integrate( g, 0.0, 2.71828 );

We can also integrate member functions by wrapping an appropriate interface around a pointer to member function and a class object (see Pointers to Member Functions Are Not Pointers [16, 57]):

template <class C>
class MFunc : public Func {
  public:
    MFunc( C &obj, double (C::*f)(double) )
        : obj_(obj), f_(f) {}
    double operator ()( double d )
        { return (obj_.*f_)( d ); }
private:
    C &obj_;
    double (C::*f_)( double );
};
//...
AClass anObj;
MFunc<AClass> f( anObj, &AClass::aFunc );
double area = integrate( f, 0.0, 2.71828 );