您的位置:首页 > 移动开发 > Objective-C

STL--Function Objects(二)

2016-06-03 00:00 666 查看
摘要: In the two subsections of this note, I present two other examples that go into more detail about function objects. The first example demonstrates how to benefit from the favt that each function object usuallu gas its own type. The second example demonstrates how to benefit from the state of funct...

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.

[code=language-cpp]#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

[code=language-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.

[code=language-cpp]//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:

You can keep the state externally and let the function object refer to it.

You can pass the function objects by reference.

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:

[code=language-cpp]#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().

[code=language-cpp]#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.

[code=language-cpp]#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:

[code=language-cpp]/*
* 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:

[code=language-cpp]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.

[code=language-cpp]#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.

[code=language-cpp]#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:

[code=language-cpp]bind(equal_to<char>(),
bind(myToupper, _1),
bind(myToupper, _2));

we create a function object calling:

[code=language-cpp]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:

[code=language-cpp]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

[code=language-cpp]#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,

[code=language-cpp]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:

[code=language-cpp]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:

[code=language-cpp]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():

[code=language-cpp]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:

[code=language-cpp]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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  C++ STL FunctionObjects