Ru-Brd |
Although pointers to functions are functors directly available in the language, there are many situations in which it is advantageous to use a class type object with an overloaded function call operator. Doing so can lead to added flexibility, added performance, or both. 22.4.1 A First Example of Class Type FunctorsHere is a very simple example of a class type functor: // functors/functor1.cpp #include <iostream> // class for function objects that return constant value class ConstantIntFunctor { private: int value; // value to return on ''function call'' public: // constructor: initialize value to return ConstantIntFunctor (int c) : value(c) { } // ''function call'' int operator() () const { return value; } }; // client function that uses the function object void client (ConstantIntFunctor const& cif) { std::cout << "calling back functor yields " << cif() << '\n'; } int main() { ConstantIntFunctor seven(7); ConstantIntFunctor fortytwo(42); client(seven); client(fortytwo); } ConstantIntFunctor is a class type from which functors can be generated. That is, if you create an object with ConstantIntFunctor seven(7); // create function object the expression seven(); // call operator () for function object is a call of operator () for the object seven rather than a call of function seven() . We achieve the same effect (indirectly) when passing the function objects seven and fortytwo through parameter cif to client() . This example illustrates what is in practice perhaps the most important advantage of class type functors over pointers to functions: the ability to associate some state (data) with the function. This is a fundamental improvement in capabilities for callback mechanisms. We can have multiple "instances" of a function with behavior that is (in a sense) parameterized. 22.4.2 Type of Class Type FunctorsThere is more to class type functors than the addition of state information, however. In fact, if a class type functor does not encapsulate any state, its behavior is entirely subsumed by its type, and it is sufficient to pass the type as a template argument to customize a library component's behavior. A classic illustration of this special case includes container classes that maintain their elements in some sorted order. The sorting criterion becomes a template argument, and because it is part of the container's type, accidental mixing of containers with different sorting criteria (for example, in an assignment) is caught by the type system. The set and map containers of the C++ standard library are parameterized this way. For example, if we define two different sets using the same element type, Person , but different sorting criteria, a comparison of the sets results in a compile-time error: #include <set> class Person { }; class PersonSortCriterion { public: bool operator() (Person const& p1, Person const& p2) const { // returns whether p1 is ''less than'' p2 } }; void foo() { std::set<Person, std::less<Person> > c0, c1; // sort with operator < std::set<Person, std::greater<Person> > c2; // sort with operator > std::set<Person, PersonSortCriterion> c3; // sort with user- // defined criterion c0 = c1; // OK: identical types c1 = c2; // ERROR: different types if (c1 == c3) { // ERROR: different types } } For all three declarations of a set , the element type and the sorting criterion are passed as template arguments. The standard function object type template std::less is defined to return the result of operator < as a result of a "function call." The following simplified implementation of std::less clarifies the idea [5] :
namespace std { template <typename T> class less { public: bool operator() (T const& x, T const& y) const { returnx<y; } }; } The std::greater template is similar. Because all three sorting criteria have different types, the resulting sets also have different types. Therefore, any attempt to assign or to compare two of these sets fails at compile time (the comparison operator requires the same type). This may seem straightforward, but prior to templates, the sorting criterion might have been maintained as a function pointer field of the container. Any mismatch would likely not have been detected until run time (and perhaps not without much frustrating detective work). |
Ru-Brd |