Understanding Function Objects
2011-08-13 00:24 Daniel Zheng 阅读(302) 评论(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.
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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.
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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.
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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.
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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;
}