分类: C/C++
2008-12-30 08:56:58
Introduction Hopefully you're aware of my format by now - once again I'm going to provide you with code you can copy and paste into an empty console application rather than use a zip file. I'll see you on the other side...
#include#include #include #include #include #include using std::binary_function; using std::unary_function; using std::accumulate; using std::cout; using std::string; using std::vector; using std::for_each; using std::ostream_iterator; using std::copy; using std::endl; using std::sort; using std::mem_fun_ref; struct Member { string Surname; string Name; int VideosOut; int DaysOverdue; Member::Member(string surname, string name, int videosOut = 0, int daysOverdue = 0) : Surname(surname), Name(name), VideosOut(videosOut), DaysOverdue(daysOverdue) {}; int Print() { cout << "Name: " << Surname <<", " << Name; if (VideosOut > 0) cout << " Videos Out = " << VideosOut; if (DaysOverdue > 0) cout << " Days overdue = " << DaysOverdue; cout << endl; return 0; } }; void PrintItem(const Member & m) { cout << "Name: " << m.Surname <<", " << m.Name; if (m.VideosOut > 0) cout << " Videos Out = " << m.VideosOut; if (m.DaysOverdue > 0) cout << " Days overdue = " << m.DaysOverdue; cout << endl; } struct CountOverdueDays : public unary_function<int, int> { CountOverdueDays(int nDayLimit) : m_nDayLimit(nDayLimit) { } int operator()(int nOverdueSoFar, const Member& m) { return m.DaysOverdue >= m_nDayLimit ? nOverdueSoFar + m.VideosOut : nOverdueSoFar; } private: const int m_nDayLimit; }; struct SortByVideosOut : public binary_function bool> { bool operator() (const Member & m1, const Member & m2) { return (m1.VideosOut < m2.VideosOut); } }; bool SortMember(const Member m1, const Member m2) { if (m1.Surname < m2.Surname) return true; else if (m1.Surname > m2.Surname) return false; return (m1.Name < m2.Name); } struct CountIntSort : public binary_function<int, int, bool> { public: static int nCount; bool operator() (const int n1, const int n2) { ++ nCount; return (n1 < n2); } }; int CountIntSort::nCount = 0; struct CountIntSort2 : public binary_function<int, int, bool> { public: int nCount; CountIntSort2() : nCount(0){}; bool operator() (const int n1, const int n2) { ++ nCount; return (n1 < n2); } }; struct CountIntSort3 : public binary_function<int, int, bool> { public: int nCount; CountIntSort3 * pParent; int nCopies; CountIntSort3() : nCount(0), nCopies(0), pParent(0) {}; CountIntSort3(CountIntSort3 & c) : nCount(0), nCopies(1) { pParent = &c; } ~CountIntSort3() { pParent->nCount += nCount; pParent->nCopies += nCopies; } bool operator() (const int n1, const int n2) { ++ nCount; return (n1 < n2); } }; int _tmain(int argc, _TCHAR* argv[]) { vector vecMem; vecMem.push_back(Member("Zaphir", "Jill")); vecMem.push_back(Member("Smith", "John", 5, 0)); vecMem.push_back(Member("Smith", "Phil", 1, 3)); vecMem.push_back(Member("Jones", "Susan")); vecMem.push_back(Member("Kaputnik", "Joe", 10, 5)); vecMem.push_back(Member("Jones", "Bill", 3, 7)); for_each(vecMem.begin(), vecMem.end(), PrintItem); cout << endl; sort(vecMem.begin(), vecMem.end(), SortMember); cout << "Now the same list sorted\n\n"; for_each(vecMem.begin(), vecMem.end(), mem_fun_ref(&Member::Print)); cout << endl; int nOverdue = accumulate(vecMem.begin(), vecMem.end(), 0 /* initval = identity of addition */, CountOverdueDays(5)); cout << "Number of videos overdue by five or more days: " << nOverdue; cout << "\n\nSorted by number of videos out:\n\n"; SortByVideosOut v; sort (vecMem.begin(), vecMem.end(), v); for_each(vecMem.begin(), vecMem.end(), mem_fun_ref(&Member::Print)); cout << endl; vector<int> vecInt; for (int i = 0; i < 10000; ++i) vecInt.push_back(rand()% 10000); CountIntSort::nCount = 0; CountIntSort c; // Use a copy so it always requires the same number of iterations vector<int> vecInt2 = vecInt; sort(vecInt2.begin(), vecInt2.end(), c); cout << "Sorting a vector of 10000 ints took " << CountIntSort::nCount << " calls to the sort function" << endl; CountIntSort2 c2; vecInt2 = vecInt; sort(vecInt2.begin(), vecInt2.end(), c2); cout << "Sorting a vector of 10000 ints second try took " << c2.nCount << " calls to the sort function" << endl; CountIntSort3 c3; vecInt2 = vecInt; sort(vecInt2.begin(), vecInt2.end(), c3); cout << "Sorting a vector of 10000 ints second try took " << c3.nCount << " calls to the sort function, and the predicate was copied " << c3.nCopies << " times." << endl; string s; std::cin >> s; return 0; }
struct Member { string Surname; string Name; int VideosOut; int DaysOverdue; Member::Member(string surname, string name, int videosOut = 0, daysOverdue = 0) : Surname(surname), Name(name), VideosOut(videosOut), DaysOverdue(daysOverdue) {}; }
vectorvecMem; vecMem.push_back(Member("Zaphir", "Jill")); vecMem.push_back(Member("Smith", "John", 5, 0)); vecMem.push_back(Member("Smith", "Phil", 1, 3)); vecMem.push_back(Member("Jones", "Susan")); vecMem.push_back(Member("Kaputnik", "Joe", 10, 5)); vecMem.push_back(Member("Jones", "Bill", 3, 7));
You're back? Good. Now, here is how it works. A function like for_each requires a unary function, one which takes one parameter. So we write a print function like this:
void PrintItem(const Member & m) { cout << "Name: " << m.Surname <<", " << m.Name; if (m.VideosOut > 0) cout << " Videos Out = " << m.VideosOut; if (m.DaysOverdue > 0) cout << " Days overdue = " << m.DaysOverdue; cout << endl; }
for_each(vecMem.begin(), vecMem.end(), PrintItem);
bool SortMember(const Member & m1, const Member & m2) { if (m1.Surname < m2.Surname) return true; else if (m1.Surname > m2.Surname) return false; return (m1.Name < m2.Name); }
sort(vecMem.begin(), vecMem.end(), SortMember);
int Print() { cout << "Name: " << Surname <<", " << Name; if (VideosOut > 0) cout << " Videos Out = " << VideosOut; if (DaysOverdue > 0) cout << " Days overdue = " << DaysOverdue; cout << endl; return 0; }
for_each(vecMem.begin(), vecMem.end(), mem_fun_ref(&Member::Print));
We can however move beyond these global functions, by instead creating a struct or class that overloads operator (). Such a function is called a functor in STL land, and a functor where operator() returns a bool is called a predicate.
Before I talk about the effect of this, let me first thank Jörgen Sigvardsson for being the first of many to point out the flaws in my original article in regard to this section. He is the author of the CoutOverdueDays function that follows.
After several false starts, I think I've got to the bottom of the issue of functors having state. Meyers explains that if operator() modifies the state of the functor, we have a problem as we are unable to know exactly how many times it will be called. However, the fact that we can introduce state into a functor is one good reason to use one. A good example can be found in Chris Losinger's article here. Jörgen's CountOverdueDays functor looks like this:
struct CountOverdueDays { CountOverdueDays(int nDayLimit) : m_nDayLimit(nDayLimit) { } int operator()(int nOverdueSoFar, const Member& m) { return m.DaysOverdue >= m_nDayLimit ? nOverdueSoFar + m.VideosOut : nOverdueSoFar; } private: const int m_nDayLimit; };
int nOverdue = accumulate(vecMem.begin(), vecMem.end(), 0 /* initval = identity of addition */, CountOverdueDays(5)); cout << "Number of videos overdue by five or more days: " << nOverdue;
struct SortByVideosOut : public binary_functionbool Member Member Member,> { bool operator() (const Member & m1, const Member & m2) { return (m1.VideosOut < m2.VideosOut); } };
SortByVideosOut v; sort (vecMem.begin(), vecMem.end(), v); for_each(vecMem.begin(), vecMem.end(), mem_fun_ref(&Member::Print));
Having come this far, I decided as an exercise to add the code to count how many times operator() is called for a common function, namely sort. The following code creates a binary predicate that does a normal sort, but also counts how often the operator is called using a static variable.
struct CountIntSort : public binary_function<int, int, bool><int int bool int int,,> { public: static int nCount; bool operator() (const int n1, const int n2) { ++ nCount; return (n1 < n2); } }; int CountIntSort::nCount = 0; And in main() vector<int> vecInt; for (int i = 0; i < 10000; ++i) vecInt.push_back(rand()% 10000); CountIntSort::nCount = 0; CountIntSort c; // Use a copy so it always requires the same number of iterations vector<int> vecInt2 = vecInt; sort(vecInt2.begin(), vecInt2.end(), c); cout << "Sorting a vector of 10000 ints took " << CountIntSort::nCount << " calls to the sort function" << endl;
struct CountIntSort2 : public binary_function<int, int, bool> { public: int nCount; CountIntSort2() : nCount(0){}; bool operator() (const int n1, const int n2) { ++ nCount; return (n1 < n2); } };
Predicates are passed by value, not by reference. Therefore the counter spins nicely inside the STL, but the predicate instance you passed in remains unaffected. A full explanation is given by Joaquín below, in his message titled 'I got it', in the thread labeled 'I still don't get why predicates should be stateless'. Suffice it to say that this will not work. So how do we get away from the static variable? We need to overload our copy constructor, and our destructor in order to maintain a count per copy, and pass it back until it returns to our original predicate. While we're at it, let's count how many copies of the predicate are made during the copy operation.
struct CountIntSort3 : public binary_function<int, int, bool> { public: int nCount; CountIntSort3 * pParent; int nCopies; CountIntSort3() : nCount(0), nCopies(0), pParent(0) {}; CountIntSort3(CountIntSort3 & c) : nCount(0), nCopies(1) { pParent = &c; } ~CountIntSort3() { pParent->nCount += nCount; pParent->nCopies += nCopies; } bool operator() (const int n1, const int n2) { ++ nCount; return (n1 < n2); } };
As has been noted before by others commenting on my first article, the Boost library provides a lot of extensions to STL, but I do not use them simply because I had a hard enough time getting everyone at work to install STLPort, and I don't see much point in learning stuff that leaves me less likely to know workarounds that can be used in every situation. I will be installing Boost when I do an article on stuff not found in the VC6 STL that can be useful, although that will mostly cover stuff in STLPort. I mention this again because I'd really like to be able to add these predicate functions to the class itself if I could.
As you can see, not only does STL come with a lot of powerful functions built in (a future article will discuss how many, for now I just try to introduce a couple in each article ), for_each provides an easy way to write our own functions, and the general mechanism of providing our own code for policy means that we can adapt the general algorithms to suit our needs. Next up, I am going to offer an article on associative containers, namely map and set. As I discovered, a set is a subset of map, and both are a very useful alternative if you're looking to keep your data sorted, or want a tradeoff between the high cost of inserting into a vector, and the high cost of searching a list.