代码改变世界

Understanding Function Objects

2011-08-13 00:24  Daniel Zheng  阅读(301)  评论(0编辑  收藏  举报

Function objects or functors might sould exotic or intimidating, but they are entities of C++ that you have probably seen if not also used, without having realized it.

The Concept of Function Objects and Predicate

On a conceptual level, function objects are objects that work as function. On an implementation level, however, function objects are objects of a class that implements operator(). Although functions and function-pointers can also be classified as function objects, it is the capability of an object of a class that implements operator() to carry state(that is, values in member attributes of the class) that makes it useful with standard template library algorithms.

Function objects as typically used by a C++ programmer working with STL are classifiable into the following type:

  • Unary function ---- A function called with one argument. When a unary function returns a bool, it is called a predicate.
  • Binary function ---- A function called with two arguments. Weh a binary function returns a bool, it is called a binary predicte.
Function objects that return a boolean type naturally find use in algorithms that need decision-making. A function object that combines two function objects is called an adaptive function object.
Typical Applications of Function Objects
it is possible to explain function objects over pages and pages of theoretical explanations. It is also possible to understand how they look and work via tiny sample applications. Let's take the practical approach and dive straight into the world of C++ programming with function objects of functors.
Unary Functions
Functions that operate on a single parameter are unary functions. A unary function can do something very simple, for example displays an element on the screen. This can be programmed as
View Code
#include <algorithm>
#include
<iostream>
#include
<vector>
#include
<list>

using namespace std;

// struct that behaves as a unary function
template <typename elementType>
struct DisplayElement
{
void operator () (const elementType& element) const
{
cout
<< element << ' ';
}
};

int main ()
{
vector
<int> vecIntegers;

for (int nCount = 0; nCount < 10; ++ nCount)
vecIntegers.push_back (nCount);

list
<char> listChars;

for (char nChar = 'a'; nChar < 'k'; ++nChar)
listChars.push_back (nChar);

cout
<< "Displaying the vector of integers: " << endl;

// Display the array of integers
for_each ( vecIntegers.begin () // Start of range
, vecIntegers.end () // End of range
, DisplayElement <int> () ); // Unary function object

cout
<< endl << endl;
cout
<< "Displaying the list of characters: " << endl;

// Display the list of characters
for_each ( listChars.begin () // Start of range
, listChars.end () // End of range
, DisplayElement <char> () );// Unary function object

return 0;
}

 

The real advantage of using a function object implemented in a struct becomes apparent when you are able to use the object of the struct to store information. This is something FuncDisplayElement cannot do the way a struct can coz a struct can have member attributes, other than the operator().  A slightly modified version that makes use of member attributes would be:

View Code
template <typename elementType>
struct DisplayElementKeepCount
{
// Hold the count in a member variable
int m_nCount;

// Constructor
DisplayElementKeepCount ()
{
m_nCount
= 0;
}

// Display the element, hold count!
void operator () (const elementType& element)
{
++ m_nCount;
cout
<< element << ' ';
}
};

  

 

The advantage of using such function objects that can also store state is seen in:

View Code
#include <algorithm>
#include
<iostream>
#include
<vector>
#include
<list>

using namespace std;

template
<typename elementType>
struct DisplayElementKeepCount
{
// Hold the count in a member variable
int m_nCount;

// Constructor
DisplayElementKeepCount ()
{
m_nCount
= 0;
}

// Display the element, hold count!
void operator () (const elementType& element)
{
++ m_nCount;
cout
<< element << ' ';
}
};

int main ()
{
vector
<int> vecIntegers;

for (int nCount = 0; nCount < 10; ++ nCount)
vecIntegers.push_back (nCount);

cout
<< "Displaying the vector of integers: " << endl;

// Display the array of integers
DisplayElementKeepCount <int> mResult;
mResult
= for_each ( vecIntegers.begin () // Start of range
, vecIntegers.end () // End of range
, DisplayElementKeepCount <int> () );// function object

cout
<< endl << endl;

// Use the state stores in the return value of for_each!
cout << "'" << mResult.m_nCount << "' elements were displayed!" << endl;

return 0;
}

  

Unary Predicate

A unary function that returns a bool is a predicate. Such functions help make decisions for STL algorithms.

View Code
template <typename numberType>
struct IsMultiple
{
numberType m_Divisor;

// divisorialize the divisor
IsMultiple (const numberType& divisor)
{
m_Divisor
= divisor;
}

// The comparator of type: bool f(x)
bool operator () (const numberType& element) const
{
// Check if the dividend is a multiple of the divisor
return ((element % m_Divisor) == 0);
}
};

We use this predicat in the below code:

View Code
#include <algorithm>
#include
<vector>
#include
<iostream>

using namespace std;

template
<typename numberType>
struct IsMultiple
{
numberType m_Divisor;

// divisorialize the divisor
IsMultiple (const numberType& divisor)
{
m_Divisor
= divisor;
}

// The comparator of type: bool f(x)
bool operator () (const numberType& element) const
{
// Check if the dividend is a multiple of the divisor
return ((element % m_Divisor) == 0);
}
};

int main ()
{
vector
<int> vecIntegers;

cout
<< "The vector contains the following sample values: ";

// Insert sample values: 25 - 31
for (int nCount = 25; nCount < 32; ++ nCount)
{
vecIntegers.push_back (nCount);
cout
<< nCount << ' ';
}

cout
<< endl;

// Find the first element that is a multiple of 4 in the collection
vector <int>::iterator iElement;
iElement
= find_if ( vecIntegers.begin ()
, vecIntegers.end ()
, IsMultiple
<int> (4) ); // Unary predicate initialized to 4

if (iElement != vecIntegers.end ())
{
cout
<< "The first element in the vector divisible by 4 is: ";
cout
<< *iElement << endl;
}

return 0;
}

 

Binary Functions

Functions of type f(x, y) are particularly useful when they return a value based on the input supplied. Such binary functions can be used for a host of arithmetic activity that involves two operands, such as addition, multiplication, subtraction, and the like. A sample binary function that returns the multiple of input arguments can be written as: 

View Code
template <typename elementType>
class CMultiply
{
public:
elementType
operator() (const elementType & elem1, const elementType & elem2)
{
return (elem1 * elem2);
}
}

  

The code below demonstrates the usage of such binary functions in std::transform.

View Code
#include <vector>
#include
<iostream>
#include
<algorithm>

template
<typename elementType>
class CMultiply
{
public:
elementType
operator () (const elementType& elem1,
const elementType& elem2)
{
return (elem1 * elem2);
}
};

int main ()
{
using namespace std;

// Create two sample vector of integers with 10 elements each
vector <int> vecMultiplicand, vecMultiplier;

// Insert sample values 0 to 9
for (int nCount1 = 0; nCount1 < 10; ++ nCount1)
vecMultiplicand.push_back (nCount1);

// Insert sample values 100 to 109
for (int nCount2 = 100; nCount2 < 110; ++ nCount2)
vecMultiplier.push_back (nCount2);

// A third container that holds the result of multiplication
vector <int> vecResult;

// Make space for the result of the multiplication
vecResult.resize (10);

transform ( vecMultiplicand.begin (),
// range of multiplicands
vecMultiplicand.end (), // end of range
vecMultiplier.begin (), // multiplier values
vecResult.begin (), // range that holds result
CMultiply <int> () ); // the function that multiplies

cout
<< "The contents of the first vector are: " << endl;
for (size_t nIndex1 = 0; nIndex1 < vecMultiplicand.size (); ++ nIndex1)
cout
<< vecMultiplicand [nIndex1] << ' ';
cout
<< endl;

cout
<< "The contents of the second vector are: " << endl;
for (size_t nIndex2 = 0; nIndex2 < vecMultiplier.size (); ++nIndex2)
cout
<< vecMultiplier [nIndex2] << ' ';
cout
<< endl << endl;

cout
<< "The result of the multiplication is: " << endl;
for (size_t nIndex = 0; nIndex < vecResult.size (); ++ nIndex)
cout
<< vecResult [nIndex] << ' ';

return 0;
}

  

Binary Predicate

A function that accpets two arguments and returns a bool is a binary predicate. Such functions find application in STL functions such as std::sort.

View Code
#include <algorithm>
#include
<string>
using namespace std;

class CCompareStringNoCase
{
public:
bool operator () (const string& str1, const string& str2) const
{
string str1LowerCase;
// Assign space
str1LowerCase.resize (str1.size ());
// Convert every character to the lower case
transform ( str1.begin (), str1.end ()
, str1LowerCase.begin (), tolower );

string str2LowerCase;
str2LowerCase.resize (str2.size ());

transform ( str2.begin (), str2.end ()
, str2LowerCase.begin (), tolower);

return (str1LowerCase < str2LowerCase);
}
};

  

Binary predicate used in associative containers such as std::set:

View Code
#include <set>
#include
<iostream>
#include
<algorithm>
#include
<string>
using namespace std;

class CCompareStringNoCase
{
public:
bool operator () (const string& str1, const string& str2) const
{
string str1LowerCase;
// Assign space
str1LowerCase.resize (str1.size ());
// Convert every character to the lower case
transform ( str1.begin (), str1.end ()
, str1LowerCase.begin (), tolower );

string str2LowerCase;
str2LowerCase.resize (str2.size ());

transform ( str2.begin (), str2.end ()
, str2LowerCase.begin (), tolower);

return (str1LowerCase < str2LowerCase);
}
};


int main ()
{
typedef
set <string, CCompareStringNoCase> SET_NAMES;

// Define a set of string to hold names
SET_NAMES setNames;

// Insert some sample names in to the set
setNames.insert ("Tina");
setNames.insert (
"jim");
setNames.insert (
"Jack");
setNames.insert (
"Sam");

cout
<< "The sample names in the set are: " << endl;

// Display the names in the set
SET_NAMES::const_iterator iNameLocator;
for ( iNameLocator = setNames.begin ()
; iNameLocator
!= setNames.end ()
;
++ iNameLocator )
cout
<< *iNameLocator << endl;

cout
<< "Enter a name you wish to search the set for: ";
string strUserInput;
cin
>> strUserInput;

SET_NAMES::iterator iNameFound
= setNames.find (strUserInput);

if (iNameFound != setNames.end ())
cout
<< "'" << *iNameFound << "' was found in the set" << endl;
else
cout
<< "Name '" << strUserInput << "' was not found in the set";

return 0;
}