STL--Function Objects(二)

Function Objects as Sorting Criteria

    Programmers often need a sorted collection of elements that have a special class.However, you either don't want to or can't use the usual operator < to sort the objects. Instead, you sort the object according to a special sorting criterion based on some member function.

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

class Person {
public:
	string firstname() const;
	string lastname() const;
	...
};

// class for function predicate
// - operator () returns whether a person is less than another person
class PersonSortCriterion {
public:
	bool operator() (const Person& p1, const Person& p2) const {
		//a person is less than another person
		//- if the last name is less
		//- if the last name is equal and the first name is less
		return p1.lastname() < p2.lastname() || 
		       (p1.lastname() == p2.lastname() && 
		        p1.firstname() < p2.firstname());
	}
};

int main()
{
	//create a set with special sorting criterion
	set<Person, PersonSortCriterion> coll;
	
	...
	
	//do something with the elements
	for(auto pos = coll.begin(); pos != coll.end(); ++pos) {
		...
	}
	
	...
}

The set coll uses the special sorting criterion PersonSortCriterion,which is defined as a function object class. PersonSortCriterion defines operator () in such a way that it compares two Persons according to their last names and, if they are equal, to their first names.The constructor of coll creates an instance of class PersonSortCriterion automatically so that the elements are sorted according to this sorting criterion.

    Note that the sorting criterion PersonSortCriterion is a type.Thus, you can use it as a template argument for the set.

 

Function Objects with Internal State

sequence1.cpp

#include <iostream>
#include <list>
#include <algorithm>
#include <iterator>
#include <cstdlib>
#include "print.hpp"
using namespace std;

class IntSequence {
private:
	int value;
	
public:
	IntSequence (int initialValue)
	  : value(initialValue) {
	}
	
	int operator() () {
		return value++;
	}
};

int main()
{
	list<int> coll;
	//insert vlaues from 1 to 9
	generate_n(back_inserter(coll),  //start
	           9,                    //number of element
	           IntSequence(1));      //generates values, starting with 1
	
	PRINT_ELEMENTS(coll);
	
	//replace second to last element but one with values starting at 42
	generate(next(coll.begin()),     //start
	         prev(coll.end()),       //end
	         IntSequence(42));       //generates values, starting with 42
	PRINT_ELEMENTS(coll);
	system("pause");
}


/*
 * output of program:
 *
 * 1 2 3 4 5 6 7 8 9
 * 1 42 43 44 45 46 47 48 9
 *
 */


// print.hpp

#include<iostream>
#include<string>

//PRINT_ELEMENTS()
// - prints optional string optstr followed by
// - all elements of the collection coll
// - in one line,separated by spaces
template <typename T>
inline void PRINT_ELEMENTS(const T& coll, const std::string& optstr="")
{
	std::cout << optstr;
	for(const auto& elem : coll) {
		std::cout << elem << ' ';
	}
	std::cout << std::endl;
}

In this example, the function object IntSequence generates a sequence of integral value.Each time operator () is called,it returns its actual vlaue and increments it.

    By default,function objects are passed by vlaue rahter than by reference.Thus, the algorithm sequare does not change the state of the function object. For example, the following code generates the sequence starting with 1 twice.

//integral sequence starting with value 1
IntSequence seq(1);

//insert sequence beginning with 1
generate_n(back_inserter(coll), 9, seq);

//insert sequence beginning with 1 again
generate_n(back_inserter(coll), 9, seq);

Algorithms can modify the state of the function objects, but you can't access and  process their final states,because they make internal copies of the function objects.however, access to the final state might be necessary.

    There are three ways to get a "result" or "feedback" from function objects passed to algorithms:

  1. You can keep the state externally and let the function object refer to it.
  2. You can pass the function objects by reference.
  3. You can use the return value of the for_each() algorithm.

 Pass function objects by reference   

To pass a function object by reference,you simply have to qualify the call of the algorithm so that the function object type is a reference. For example:

#include <iostream>
#include <list>
#include <algorithm>
#include <iterator>
#include <cstdlib>
#include "print.hpp"
using namespace std;

class IntSequence {
private:
	int value;
	
public:
	//constructor
	IntSequence(int initialValue)
	  : value(initialValue) {
	}
	
	//"function call"
	int operator() () {
		return value++;
	}
};


int main()
{
	list<int> coll;
	IntSequence seq(1);    //integral sequence starting with 1
	
	// insert values from 1 to 4
	// - pass function object by reference
	// so that it will continue with 5
	generate_n<back_insert_iterator<list<int>>, int, IntSequence&> (back_inserter(coll),
	                                                                4,
	                                                                seq);
	PRINT_ELEMENTS(coll);
	
	//insert values from 42 to 45
	generate_n(back_inserter(coll),      //start
	           4,                        //number of elements
	           IntSequence(42));         //generates values
    PRINT_ELEMENTS(coll);
    
    //continue with first sequence
    //- pass function object by value
    //so that it will continue with 5 again
    generate_n(back_inserter(coll),      //start
               4,                        //number of elements
               seq);                     //generates values
    PRINT_ELEMENTS(coll);
    
    //continue with first sequence
    generate_n(back_inserter(coll),      //start
               4,                        //number of elements
               seq);                     //generates values
	PRINT_ELEMENTS(coll);
	
	system("pause");
}



/*
 * output of program:
 *
 * 1 2 3 4
 * 1 2 3 4 42 43 44 45
 * 1 2 3 4 42 43 44 45 5 6 7 8
 * 1 2 3 4 42 43 44 45 5 6 7 8 5 6 7 8
 *
 */
 


// print.hpp

#include<iostream>
#include<string>

//PRINT_ELEMENTS()
// - prints optional string optstr followed by
// - all elements of the collection coll
// - in one line,separated by spaces
template <typename T>
inline void PRINT_ELEMENTS(const T& coll, const std::string& optstr="")
{
	std::cout << optstr;
	for(const auto& elem : coll) {
		std::cout << elem << ' ';
	}
	std::cout << std::endl;
}

The return value of for_each()

The effort involved with passing a function object by refernece in order to access its final state is not necessary if you use the for_each() algorithm. for_each() has the unique ability to return its function object.Thus,you can query the state of your function object by checking the return value of for_each().

#include <iostream>
#include <vector>
#include <algorithm>
#include <cstdlib>
using namespace std;

//function object to process the mean value
class MeanValue {
private:
	long num;    //number of elements
	long sum;    //sum of all element values
	
public:
	//constructor
	MeanValue()
	  : num(0)
	  , sum(0)
	{ }
	
	//"function call"
	//- process one more element of the sequence
	void operator() (int elem) {
		++num;         //increment cout
		sum += elem;   //add value
	}
	
	//return mean value
	double value() {
		return static_cast<double>(sum) / static_cast<double>(num);
	}
};


int main()
{
	vector<int> coll = { 1, 2, 3, 4, 5, 6, 7, 8 };
	
	//process and print mean value
	MeanValue mv = for_each(coll.begin(), coll.end(),    //range
	                        MeanValue());                //operation
	cout << "mean value: " << mv.value() << endl;
	/*
	 * The function object is returned and assigned to mv,so you can query its 
	 * state after the statement by calling: mv.value().
	 */
	
	system("pause");
}


/*
 * output of program:
 *
 * mean value: 4.5
 *
 */

Note that lambdas provide a more convenient way to specify this behavior.However,that does not mean that lambdas are always better than fucntion objects.Function objects are more convenient when their type is required,such as for a declaration of a hash function, sorting, or equivalence criterion of associative or unordered containers.The fact that a function object is usually globally introduced helps to provide them in header file or libraries, whereas lambdas are better for specific behavior specified locally.

 

Preficates versus Function Objects

Predicates are functions or function objects that return a Boolean value. However,not every function that returns a boolean value is a valid predicate for the STL.

#include <iostream>
#include <list>
#include <algorithm>
#include <cstdlib>
#include "print.hpp"
using namespace std;

class Nth {        //fucntion object that returns true for the nth call
private:
	int nth;       //call for which to return true
	int count;     //call counter
	
public:
	Nth(int n)
	  : nth(n), count(0) {
	}
	bool operator() (int) {
		return ++count == nth;
	}
};

int main()
{
	list<int> coll = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	PRINT_ELEMENTS(coll, "coll:         ");
	
	//remove third element
	list<int>::iterator pos;
	pos = remove_if(coll.begin(), coll.end(),    //range
	                Nth(3));                     //remove criterion
    coll.erase(pos, coll.end());
    
    PRINT_ELEMENTS(coll, "3rd removed:  ");
	
	system("pause");
}

The result is a big surprise:

/*
 * output of program:
 *
 * coll:         1 2 3 4 5 6 7 8 9 10
 * 3rd removed:  1 2 4 5 7 8 9 10
 *
 */

Two elements, the third and sixth elements,are removed. This happens because the usual implementation of the algorithm copies the predicate internally during the algorithm:

template<typename ForwIter, typename Predicate>
ForwIter std::remove_if(ForwIter beg, ForwIter end, Predicate op)
{
    beg = find_if(beg, end, op);
    if(beg == end) {
        return beg;
    }
    else {
        ForwIter next = beg;
        return remove_copy_if(++next, end, beg, op);
    }
};

The algorithm uses find_if() to find the first element that should be removed. However, the algorithm then uses a copy of the passed predicate op to process the remaining elements. Here,Nth in its original state is used again and also removes the third element of the remaining elements, which is in sixth element.

    This behavior is not a bug.The standard does not specify how often a predicate might be copied internally by an algorithm.Thus, to get the behaviorof the C++ standard library,you should not pass a function object for which the behavior depends on how often it is copied or called.Thus, if you call a unary predicate for two arguments and both arguments are equal,the predicate should always yield the same result.

 

Predefined Function Objects

Predefined Function Objects

ExpressionEffect
 negate<type>() - param
 plus<type>() param1 + param2
 minus<type>() param1 - param2
 multiplies<type>() param1 * param2
 divides<type>() param1 / param2
 modulus<type>() param1 % param2
 equal_to<type>() param1 == param2
 not_equal_to<type>() param1 != param2
 less<type>() param1 < param2
 greater<type>() param1 > param2
 less_equal<type>() param1 <= param2
 greater_equal<type>() param1 >= param2
 logical_not<type>() !param
 logucal_and<type>() param1 && param2
 logical_or<type>() param1 || param2
 bit_and<type>() param1 & param2
 bit_or<type>() param1 | param2
 bit_xor<type>() param1 ^ param2
  

 

Function Adapters and Binders

A function adapter is a function object that enables the composition of function objects with each other, with certain value, or with special functions.

The most important adapter is bind(). It allows you to:

  • Adapter and compose new function objects out of existing or predefined function objects
  • Call global functions
  • Call member functions for objects,pointers to objects, and smart pointers to objects

The Bind() Adapter

In general, bind() binds parameters for callable objects. Thus if a function, member function, function object, or lambda requires some parameters, you can bind them to specific or passed argument.Specific argumentd you simply name.For passed arguments, you can use the predefined placeholders _1, _2,...defined in namespace std::placeholders.

#include <functional>
#include <iostream>
#include <cstdlib>

int main()
{
	auto plus10 = std::bind(std::plus<int>(),
		                    std::placeholders::_1,
		                    10);
    std::cout << "+10:  " << plus10(7) << std::endl;
    
    auto plus10times2 = std::bind(std::multiplies<int>(),
    	                          std::bind(std::plus<int>(),
    	                          	        std::placeholders::_1,
    	                                    10),
    	                          2);
	std::cout << "+10 * 2:  " << plus10times2(7) << std::endl;
	
	auto pow3 = std::bind(std::multiplies<int>(),
		                  std::bind(std::multiplies<int>(),
		                  	        std::placeholders::_1,
		                  	        std::placeholders::_1),
		                  std::placeholders::_1);
	std::cout << "x*x*x:  " << pow3(7) << std::endl;
	
	auto inversDivide = std::bind(std::divides<double>(),
		                          std::placeholders::_2,
		                          std::placeholders::_1);
	std::cout << "invdiv:  " << inversDivide(49, 7) << std::endl;
	
	system("pause");
}


/*
 * output of program:
 *
 * +10:  17
 * +10 * 2:  34
 * x*x*x:  343
 * invdiv:  0.142857
 *
 */

Calling Global Functions

The following example demonstrates how bind() can be used to call global functions.

#include <iostream>
#include <algorithm>
#include <functional>
#include <locale>
#include <string>
#include <cstdlib>
using namespace std;
using namespace std::placeholders;
	
char myToupper(char c)
{
	std::locale loc;
		return std::use_facet<std::ctype<char>>(loc).toupper(c);
}

int main()
{
	string s("Internationalization");
	string sub("Nation");
	
	//search substring case insensitive
	string::iterator pos;
	pos = search(s.begin(), s.end(),            //string to search in
	             sub.begin(), sub.end(),        //substring to search
	             bind(equal_to<char>(),         //compar criterion
	                  bind(myToupper, _1),
	                  bind(myToupper, _2)));
	if(pos != s.end()) {
		cout << "\"" << sub << "\" is part of \"" << s << "\"" << endl;
	}
	
	system("pause");
}


/*
 * output of program:
 *
 * "Nation" is part of "Internationalization"
 *
 */

Here, we use the search() algorithm to check whether sub is a substring in s,when case sensitivity doesn't matter.With:

bind(equal_to<char>(),
     bind(myToupper, _1),
     bind(myToupper, _2));

we create a function object calling:

myToupper(param1) == myToupper(param2)

where myToupper() is our convenience function to convert the characters of the strings into uppercase.

    Note that bind() internally copies passed arguments.To let the function object use a reference to a passed argument, use ref() or cref(). For example:

void incr(int& i)
{
    ++i;
}

int i = 0;
bind(incr, i) ();        //increments a copy of i, no effect for i
bind(incr, ref(i)) ();   //increments i

Calling Member Functions

#include <functional>
#include <algorithm>
#include <vector>
#include <iostream>
#include <string>
#include <cstdlib>
using namespace std;
using namespace std::placeholders;

class Person {
private:
	string name;
public:
	Person(const string& n)
	  :name(n) {
	}
	
	void print () const {
		cout << name << endl;
	}
	
	void print2 (const string& prefix) const {
		cout << prefix << name << endl;
	}
};

int main()
{
	vector<Person> coll = { Person("Tick"), Person("Trick"), Person("Track") };
	
	//call member function print() for each person
	for_each(coll.begin(), coll.end(),
	         bind(&Person::print, _1));
	cout << endl;
	
	//call member function print2() with additional argument for each Person
	for_each(coll.begin(), coll.end(),
	         bind(&Person::print2, _1, "Person: "));
	cout << endl;
	
	//call print2() for temporary Person
	bind(&Person::print2, _1, "This is: ")(Person("nico"));
	
	system("pause");
}


/*
 * output of program:
 *
 * Tick
 * Trick
 * Track
 *
 * Person: Tick
 * Person: Trick
 * Person: Track
 *
 * This is: nico
 *
 */

Here,

bind(&Person::print, _1);

defines a function object that calls param1.print() for passed Person.That is, beacuse the first argument is a member function,the next argument defines the object for which this member function gets called.

    Any additional argument is a passed to the member function.That means:

bind(&Person::print2, _1, "Person: ");

 defines a function object that calls param1.print2("Person: ") for any passed Person.

    Here, the passed objects are the members of coll, but in principle, you can pass objects directly.For example:

Person n("nico");
bind(&Person::print2, _1, "This is: ")(n0;

calls n.print2("This is: ").

    Note that you can also pass pointers to objects and even smart pointers to bind():

vector<Person*> cp;
for_each(cp.begin(), cp.end(),
         bind(&Person::print, _1));

vector<shared_ptr<Person>> sp;
for_each(sp.begin(), sp.end(),
         bind(&Person::print, _1));

    Note that you can also call modifying member functions:

class Person {
public:
    ...
    void setName (const string& n) {
        name = n;
    }
};

vector<Person> coll;
...
for_each(coll.begin(), coll.end(),               //give all Persons same name
         bind(&Person::setName, _1, "Paul"));

 

See detail in The C++ Standard Library, p475.

 

STL--Function Objects(一)    http://my.oschina.net/daowuming/blog/685188

转载于:https://my.oschina.net/daowuming/blog/686879

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值