C++ uses parentheses to implement three critical features: grouping expressions, casting data from one type to another, and calling functions. The Conversion Operations section presented earlier in the chapter demonstrated how C++ programmers override the casting operator to create new type conversions. This section demonstrates how programmers can overload the function call operator. An instance of a class that overloads the function call operator is called a function object or a functor. The C++ syntax for invoking a functor is indistinguishable from that of a "normal" function call.
class Foo
{
public:
Foo(parameters)
return-type operator()(parameter-list) { ... } // (a)
}; |
Foo functor(parameters); // (b) return-type result = functor(args); // (c) |
Like all overloaded operators, functors, and the function call operator only represent one possible problem solution. Programmers can replace functors with "normal" or classic functions or function pointers. However, functors offer a convenient, compact, and familiar syntax. Functors accrue all class benefits, making them superior to many of their potential replacements. Specifically, they can have functions beyond the overloaded function call operator, including constructors, other members (such as helpers), and friends. Maintaining data as private member variables allows functors to "remember" it between calls and share it with other class functions, while limiting its scope and restricting access to it. The following examples demonstrate some of the ways programmers use functors.
In this context, state refers to the values currently stored in a functor's member variables and retained between function calls. C++ programmers can achieve similar results using the static keyword albeit with less flexibility
class NPL
{
private:
long ix;
long iy;
long iz;
public:
NPL(int seed1, int seed2, int seed3) :
ix(seed1), iy(seed2), iz(seed3) {}
double operator()();
long operator()(long max);
};
|
double NPL::operator()() { ix = 171 * ix % 30269L; iy = 172 * iy % 30307L; iz = 170 * iz % 30323L; return fmod(ix / 30269.0 + iy / 30307.0 + iz / 30323.0, 1); } long NPL::operator()(long max) { return (long)fmod(max * operator()(), max); //return (long)fmod(max * (*this)(), max); //return (long)(floor(max * operator())) % max; } |
| (a) | (b) |
#include <iostream>
using namespace std;
int main()
{
NPL npl(41, 67, 91);
for (int i = 100; i < 110; i++)
cout < npl() << " " << npl() << endl;
return 0;
} |
|
| (c) | |
this pointer references or points to the functor, but the overloaded function call operator requires an object rather than a pointer. The asterisk dereferences this, but the dereference expression must be surrounded by grouping parentheses because the dereference operator has a lower precedence than the function call. This syntax may not seem "familiar" as claimed above, but it is when viewed in the application context, (c). % .Some programmers may find the general syntax defining function pointers intimidating and off putting. They can replace function pointers with function objects, also known as functors. Although the idea of functors may be unfamiliar, their implementation only requires object-oriented concepts that should look familiar once explained. Understanding functors and their syntax is less challenging than understanding the problems and programs using them. The following example demonstrates a specialized functor called a comparator used in conjunction with the C++ min library function. Given two objects, the min function determines which is the "smallest," comparing them with an application-defined comparator, which operates like a synchronous callback function. The example attempts to balance simplicity with authenticity by omitting some of the generalizing features of the min function, revisiting them later in the Templates chapter.
#include <iostream>
#include <iomanip>
#include <string>
#include <algorithm> // for the sort function
using namespace std;
enum { OFFICER, MANAGER, ENGINEER };
class Employee
{
private:
string name;
int position;
int id;
public:
Employee(string n, int pos, int i) :
name(n), position(pos), id(i) {}
string getName() const { return name; }
int getPosition() const { return position; }
int getID() const { return id; }
friend ostream& operator<<(ostream& out,
const Employee& me)
{
out << left << setw(10) << me.name << setw(3)
<< me.position << setw(5) << me.id;
return out;
}
}; |
int main()
{
Employee e1("Dilbert", ENGINEER, 400);
Employee e2("Alice", ENGINEER, 100);
Employee e3("Wally", ENGINEER, 200);
Employee e4("Asok", ENGINEER, 700);
Employee e5("PHB", MANAGER, 600);
Employee e6("Richard", MANAGER, 500);
Employee e7("Catbert", OFFICER, 300);
cout << min(e1, e2, compByNumber()) << endl;
cout << min(e2, e5, compByNumber()) << endl;
cout << min(e5, e5, compByNumber()) << endl;
cout << min(e5, e7, compByNumber()) << endl;
cout << endl;
cout << min(e1, e2, compByName()) << endl;
cout << min(e2, e5, compByName()) << endl;
cout << min(e5, e5, compByName()) << endl;
cout << min(e5, e7, compByName()) << endl;
return 0;
} |
| (a) | (b) |
class compByNumber
{
public:
bool operator()(const Employee& e1,
const Employee& e2) const
{
if (e1.getPosition() == e2.getPosition())
return e1.getID() <= e2.getID();
return e1.getPosition() <= e2.getPosition();
}
}; |
class compByName
{
public:
bool operator()(const Employee& e1,
const Employee& e2) const
{
return e1.getName() <= e2.getName();
}
}; |
| (c) | (d) |
const value, causing the program to use it throughout.operator(). Using them, highlighted in blue and pink, is straightforward. The first two arguments are Employee objects, and the third is the comparator.| View | Download | Comments |
|---|---|---|
| npl.h | npl.h | The NPL pseudo-random number generator class specification |
| npl.cpp | npl.cpp | The NPL pseudo-random number generator member functions |
| driver.cpp | driver.cpp | A simple application demonstrating the NPL PRNG |
| Employee.cpp | Employee.cpp | The complete Employee and comparator example |