- •Contents
- •Introduction
- •Who This Book Is For
- •What This Book Covers
- •How This Book Is Structured
- •What You Need to Use This Book
- •Conventions
- •Source Code
- •Errata
- •p2p.wrox.com
- •The Basics of C++
- •The Obligatory Hello, World
- •Namespaces
- •Variables
- •Operators
- •Types
- •Conditionals
- •Loops
- •Arrays
- •Functions
- •Those Are the Basics
- •Diving Deeper into C++
- •Pointers and Dynamic Memory
- •Strings in C++
- •References
- •Exceptions
- •The Many Uses of const
- •C++ as an Object-Oriented Language
- •Declaring a Class
- •Your First Useful C++ Program
- •An Employee Records System
- •The Employee Class
- •The Database Class
- •The User Interface
- •Evaluating the Program
- •What Is Programming Design?
- •The Importance of Programming Design
- •Two Rules for C++ Design
- •Abstraction
- •Reuse
- •Designing a Chess Program
- •Requirements
- •Design Steps
- •An Object-Oriented View of the World
- •Am I Thinking Procedurally?
- •The Object-Oriented Philosophy
- •Living in a World of Objects
- •Object Relationships
- •Abstraction
- •Reusing Code
- •A Note on Terminology
- •Deciding Whether or Not to Reuse Code
- •Strategies for Reusing Code
- •Bundling Third-Party Applications
- •Open-Source Libraries
- •The C++ Standard Library
- •Designing with Patterns and Techniques
- •Design Techniques
- •Design Patterns
- •The Reuse Philosophy
- •How to Design Reusable Code
- •Use Abstraction
- •Structure Your Code for Optimal Reuse
- •Design Usable Interfaces
- •Reconciling Generality and Ease of Use
- •The Need for Process
- •Software Life-Cycle Models
- •The Stagewise and Waterfall Models
- •The Spiral Method
- •The Rational Unified Process
- •Software-Engineering Methodologies
- •Extreme Programming (XP)
- •Software Triage
- •Be Open to New Ideas
- •Bring New Ideas to the Table
- •Thinking Ahead
- •Keeping It Clear
- •Elements of Good Style
- •Documenting Your Code
- •Reasons to Write Comments
- •Commenting Styles
- •Comments in This Book
- •Decomposition
- •Decomposition through Refactoring
- •Decomposition by Design
- •Decomposition in This Book
- •Naming
- •Choosing a Good Name
- •Naming Conventions
- •Using Language Features with Style
- •Use Constants
- •Take Advantage of const Variables
- •Use References Instead of Pointers
- •Use Custom Exceptions
- •Formatting
- •The Curly Brace Alignment Debate
- •Coming to Blows over Spaces and Parentheses
- •Spaces and Tabs
- •Stylistic Challenges
- •Introducing the Spreadsheet Example
- •Writing Classes
- •Class Definitions
- •Defining Methods
- •Using Objects
- •Object Life Cycles
- •Object Creation
- •Object Destruction
- •Assigning to Objects
- •Distinguishing Copying from Assignment
- •The Spreadsheet Class
- •Freeing Memory with Destructors
- •Handling Copying and Assignment
- •Different Kinds of Data Members
- •Static Data Members
- •Const Data Members
- •Reference Data Members
- •Const Reference Data Members
- •More about Methods
- •Static Methods
- •Const Methods
- •Method Overloading
- •Default Parameters
- •Inline Methods
- •Nested Classes
- •Friends
- •Operator Overloading
- •Implementing Addition
- •Overloading Arithmetic Operators
- •Overloading Comparison Operators
- •Building Types with Operator Overloading
- •Pointers to Methods and Members
- •Building Abstract Classes
- •Using Interface and Implementation Classes
- •Building Classes with Inheritance
- •Extending Classes
- •Overriding Methods
- •Inheritance for Reuse
- •The WeatherPrediction Class
- •Adding Functionality in a Subclass
- •Replacing Functionality in a Subclass
- •Respect Your Parents
- •Parent Constructors
- •Parent Destructors
- •Referring to Parent Data
- •Casting Up and Down
- •Inheritance for Polymorphism
- •Return of the Spreadsheet
- •Designing the Polymorphic Spreadsheet Cell
- •The Spreadsheet Cell Base Class
- •The Individual Subclasses
- •Leveraging Polymorphism
- •Future Considerations
- •Multiple Inheritance
- •Inheriting from Multiple Classes
- •Naming Collisions and Ambiguous Base Classes
- •Interesting and Obscure Inheritance Issues
- •Special Cases in Overriding Methods
- •Copy Constructors and the Equals Operator
- •The Truth about Virtual
- •Runtime Type Facilities
- •Non-Public Inheritance
- •Virtual Base Classes
- •Class Templates
- •Writing a Class Template
- •How the Compiler Processes Templates
- •Distributing Template Code between Files
- •Template Parameters
- •Method Templates
- •Template Class Specialization
- •Subclassing Template Classes
- •Inheritance versus Specialization
- •Function Templates
- •Function Template Specialization
- •Function Template Overloading
- •Friend Function Templates of Class Templates
- •Advanced Templates
- •More about Template Parameters
- •Template Class Partial Specialization
- •Emulating Function Partial Specialization with Overloading
- •Template Recursion
- •References
- •Reference Variables
- •Reference Data Members
- •Reference Parameters
- •Reference Return Values
- •Deciding between References and Pointers
- •Keyword Confusion
- •The const Keyword
- •The static Keyword
- •Order of Initialization of Nonlocal Variables
- •Types and Casts
- •typedefs
- •Casts
- •Scope Resolution
- •Header Files
- •C Utilities
- •Variable-Length Argument Lists
- •Preprocessor Macros
- •How to Picture Memory
- •Allocation and Deallocation
- •Arrays
- •Working with Pointers
- •Array-Pointer Duality
- •Arrays Are Pointers!
- •Not All Pointers Are Arrays!
- •Dynamic Strings
- •C-Style Strings
- •String Literals
- •The C++ string Class
- •Pointer Arithmetic
- •Custom Memory Management
- •Garbage Collection
- •Object Pools
- •Function Pointers
- •Underallocating Strings
- •Memory Leaks
- •Double-Deleting and Invalid Pointers
- •Accessing Out-of-Bounds Memory
- •Using Streams
- •What Is a Stream, Anyway?
- •Stream Sources and Destinations
- •Output with Streams
- •Input with Streams
- •Input and Output with Objects
- •String Streams
- •File Streams
- •Jumping around with seek() and tell()
- •Linking Streams Together
- •Bidirectional I/O
- •Internationalization
- •Wide Characters
- •Non-Western Character Sets
- •Locales and Facets
- •Errors and Exceptions
- •What Are Exceptions, Anyway?
- •Why Exceptions in C++ Are a Good Thing
- •Why Exceptions in C++ Are a Bad Thing
- •Our Recommendation
- •Exception Mechanics
- •Throwing and Catching Exceptions
- •Exception Types
- •Throwing and Catching Multiple Exceptions
- •Uncaught Exceptions
- •Throw Lists
- •Exceptions and Polymorphism
- •The Standard Exception Hierarchy
- •Catching Exceptions in a Class Hierarchy
- •Writing Your Own Exception Classes
- •Stack Unwinding and Cleanup
- •Catch, Cleanup, and Rethrow
- •Use Smart Pointers
- •Common Error-Handling Issues
- •Memory Allocation Errors
- •Errors in Constructors
- •Errors in Destructors
- •Putting It All Together
- •Why Overload Operators?
- •Limitations to Operator Overloading
- •Choices in Operator Overloading
- •Summary of Overloadable Operators
- •Overloading the Arithmetic Operators
- •Overloading Unary Minus and Unary Plus
- •Overloading Increment and Decrement
- •Overloading the Subscripting Operator
- •Providing Read-Only Access with operator[]
- •Non-Integral Array Indices
- •Overloading the Function Call Operator
- •Overloading the Dereferencing Operators
- •Implementing operator*
- •Implementing operator->
- •What in the World Is operator->* ?
- •Writing Conversion Operators
- •Ambiguity Problems with Conversion Operators
- •Conversions for Boolean Expressions
- •How new and delete Really Work
- •Overloading operator new and operator delete
- •Overloading operator new and operator delete with Extra Parameters
- •Two Approaches to Efficiency
- •Two Kinds of Programs
- •Is C++ an Inefficient Language?
- •Language-Level Efficiency
- •Handle Objects Efficiently
- •Use Inline Methods and Functions
- •Design-Level Efficiency
- •Cache as Much as Possible
- •Use Object Pools
- •Use Thread Pools
- •Profiling
- •Profiling Example with gprof
- •Cross-Platform Development
- •Architecture Issues
- •Implementation Issues
- •Platform-Specific Features
- •Cross-Language Development
- •Mixing C and C++
- •Shifting Paradigms
- •Linking with C Code
- •Mixing Java and C++ with JNI
- •Mixing C++ with Perl and Shell Scripts
- •Mixing C++ with Assembly Code
- •Quality Control
- •Whose Responsibility Is Testing?
- •The Life Cycle of a Bug
- •Bug-Tracking Tools
- •Unit Testing
- •Approaches to Unit Testing
- •The Unit Testing Process
- •Unit Testing in Action
- •Higher-Level Testing
- •Integration Tests
- •System Tests
- •Regression Tests
- •Tips for Successful Testing
- •The Fundamental Law of Debugging
- •Bug Taxonomies
- •Avoiding Bugs
- •Planning for Bugs
- •Error Logging
- •Debug Traces
- •Asserts
- •Debugging Techniques
- •Reproducing Bugs
- •Debugging Reproducible Bugs
- •Debugging Nonreproducible Bugs
- •Debugging Memory Problems
- •Debugging Multithreaded Programs
- •Debugging Example: Article Citations
- •Lessons from the ArticleCitations Example
- •Requirements on Elements
- •Exceptions and Error Checking
- •Iterators
- •Sequential Containers
- •Vector
- •The vector<bool> Specialization
- •deque
- •list
- •Container Adapters
- •queue
- •priority_queue
- •stack
- •Associative Containers
- •The pair Utility Class
- •multimap
- •multiset
- •Other Containers
- •Arrays as STL Containers
- •Strings as STL Containers
- •Streams as STL Containers
- •bitset
- •The find() and find_if() Algorithms
- •The accumulate() Algorithms
- •Function Objects
- •Arithmetic Function Objects
- •Comparison Function Objects
- •Logical Function Objects
- •Function Object Adapters
- •Writing Your Own Function Objects
- •Algorithm Details
- •Utility Algorithms
- •Nonmodifying Algorithms
- •Modifying Algorithms
- •Sorting Algorithms
- •Set Algorithms
- •The Voter Registration Audit Problem Statement
- •The auditVoterRolls() Function
- •The getDuplicates() Function
- •The RemoveNames Functor
- •The NameInList Functor
- •Testing the auditVoterRolls() Function
- •Allocators
- •Iterator Adapters
- •Reverse Iterators
- •Stream Iterators
- •Insert Iterators
- •Extending the STL
- •Why Extend the STL?
- •Writing an STL Algorithm
- •Writing an STL Container
- •The Appeal of Distributed Computing
- •Distribution for Scalability
- •Distribution for Reliability
- •Distribution for Centrality
- •Distributed Content
- •Distributed versus Networked
- •Distributed Objects
- •Serialization and Marshalling
- •Remote Procedure Calls
- •CORBA
- •Interface Definition Language
- •Implementing the Class
- •Using the Objects
- •A Crash Course in XML
- •XML as a Distributed Object Technology
- •Generating and Parsing XML in C++
- •XML Validation
- •Building a Distributed Object with XML
- •SOAP (Simple Object Access Protocol)
- •. . . Write a Class
- •. . . Subclass an Existing Class
- •. . . Throw and Catch Exceptions
- •. . . Read from a File
- •. . . Write to a File
- •. . . Write a Template Class
- •There Must Be a Better Way
- •Smart Pointers with Reference Counting
- •Double Dispatch
- •Mix-In Classes
- •Object-Oriented Frameworks
- •Working with Frameworks
- •The Model-View-Controller Paradigm
- •The Singleton Pattern
- •Example: A Logging Mechanism
- •Implementation of a Singleton
- •Using a Singleton
- •Example: A Car Factory Simulation
- •Implementation of a Factory
- •Using a Factory
- •Other Uses of Factories
- •The Proxy Pattern
- •Example: Hiding Network Connectivity Issues
- •Implementation of a Proxy
- •Using a Proxy
- •The Adapter Pattern
- •Example: Adapting an XML Library
- •Implementation of an Adapter
- •Using an Adapter
- •The Decorator Pattern
- •Example: Defining Styles in Web Pages
- •Implementation of a Decorator
- •Using a Decorator
- •The Chain of Responsibility Pattern
- •Example: Event Handling
- •Implementation of a Chain of Responsibility
- •Using a Chain of Responsibility
- •Example: Event Handling
- •Implementation of an Observer
- •Using an Observer
- •Chapter 1: A Crash Course in C++
- •Chapter 3: Designing with Objects
- •Chapter 4: Designing with Libraries and Patterns
- •Chapter 5: Designing for Reuse
- •Chapter 7: Coding with Style
- •Chapters 8 and 9: Classes and Objects
- •Chapter 11: Writing Generic Code with Templates
- •Chapter 14: Demystifying C++ I/O
- •Chapter 15: Handling Errors
- •Chapter 16: Overloading C++ Operators
- •Chapter 17: Writing Efficient C++
- •Chapter 19: Becoming Adept at Testing
- •Chapter 20: Conquering Debugging
- •Chapter 24: Exploring Distributed Objects
- •Chapter 26: Applying Design Patterns
- •Beginning C++
- •General C++
- •I/O Streams
- •The C++ Standard Library
- •C++ Templates
- •Integrating C++ and Other Languages
- •Algorithms and Data Structures
- •Open-Source Software
- •Software-Engineering Methodology
- •Programming Style
- •Computer Architecture
- •Efficiency
- •Testing
- •Debugging
- •Distributed Objects
- •CORBA
- •XML and SOAP
- •Design Patterns
- •Index
Mastering STL Algorithms and Function Objects
either unary_function or binary_function, depending on whether they take one or two arguments. These two classes, both defined in <functional>, are templatized on the parameter and return types of the “function” they provide. For example, instead of using ptr_fun() to convert isdigit(), you could write a wrapper function object like this:
#include <functional> #include <algorithm> #include <cctype> #include <string> using namespace std;
class myIsDigit : public unary_function<char, bool>
{
public:
bool operator() (char c) const { return (::isdigit(c)); }
};
bool isNumber(const string& str)
{
string::const_iterator it = find_if(str.begin(), str.end(), not1(myIsDigit()));
return (it == str.end());
}
Note that the overloaded function call operator of the myIsDigit class must be const in order to pass objects to find_if().
The algorithms are allowed to make multiple copies of function object predicates and call different ones for different elements. Thus, you shouldn’t write them such that they count on any internal state to the object being consistent between calls.
Algorithm Details
This chapter describes the general categories of algorithms, with examples of each. The Standard Library Reference resource on the Web site contains a summary of all the algorithms, but for nitty-gritty details, you should consult one of the books on the STL listed in Appendix B.
Recall from Chapter 21 that there are five types of iterators: input, output, forward, bidirectional, and random-access. There is no formal class hierarchy of these iterators, because the implementations for each container are not part of the standard hierarchy. However, one can deduce a hierarchy based on the functionality they are required to provide. Specifically, every random access iterator is also bidirectional, every bidirectional iterator is also forward, and every forward iterator is also input and output.
The standard way for the algorithms to specify what kind of iterators they need is to use the following names for the iterator template arguments: InputIterator, OutputIterator, ForwardIterator, BidirectionalIterator, and RandomAccessIterator. These names are just names: they don’t provide binding type checking. Therefore, you could, for example, try to call an algorithm expecting a
631
Chapter 22
RandomAccessIterator by passing a bidirectional iterator. The template doesn’t do type checking, so it would allow this instantiation. However, the code in the function that uses the random access iterator capabilities would fail to compile on the bidirectional iterator. Thus, the requirement is enforced, just not where you would expect. The error message can therefore be somewhat confusing. For example, attempting to use the generic sort() algorithm, which requires a random access iterator, on a list, which provides only a bidirectional iterator, gives this error in g++:
/usr/include/c++/3.2.2/bits/stl_algo.h: In function `void std::sort(_RandomAccessIter, _RandomAccessIter) [with _RandomAccessIter = std::_List_iterator<int, int&, int*>]’:
Sorting.cpp:38: instantiated from here /usr/include/c++/3.2.2/bits/stl_algo.h:2178: no match for `
std::_List_iterator<int, int&, int*>& - std::_List_iterator<int, int&, int*>&’ operator
Don’t worry if you don’t understand this error yet. The sort() algorithm is covered later in this chapter.
Most of the algorithms are defined in the <algorithm> header file, but a few algorithms are located in <numeric>. They are all in the std namespace. See the Standard Library Reference resource on the Web site for details.
Utility Algorithms
The STL provides three utility algorithms implemented as function templates: min(), max(), and swap(). min() and max() compare two elements of any type with operator< or a user-supplied binary predicate, returning a reference to the smaller or larger element, respectively. swap() takes two elements of any type by reference and switches their values.
These utilities do not work on sequences of elements, so they do not take iterator parameters.
The following program demonstrates the three functions:
#include <algorithm> #include <iostream> using namespace std;
int main(int argc, char** argv)
{
int x = 4, y = 5;
cout << “x is “ << x << “ and y is “ << y << endl; cout << “Max is “ << max(x, y) << endl;
cout << “Min is “ << min(x, y) << endl; swap(x, y);
cout << “x is “ << x << “ and y is “ << y << endl; cout << “Max is “ << max(x, y) << endl;
cout << “Min is “ << min(x, y) << endl;
return (0);
}
632
Mastering STL Algorithms and Function Objects
Here is the program output:
x is 4 and y is 5 Max is 5
Min is 4
x is 5 and y is 4 Max is 5
Min is 4
Nonmodifying Algorithms
The nonmodifying algorithms include functions for searching elements in a range, generating numerical information about elements in a range, comparing two ranges to each other, and processing each element in a range.
Search Algorithms
You’ve already seen two examples of search algorithms: find() and find_if(). The STL provides several other variations of the basic find() algorithm that work on unsorted sequences of elements. adjacent_find() finds the first instance of two consecutive elements that are equal to each other. find_first_of() searches for one of several values simultaneously. search() and find_end() search for subsequences matching a specified sequence of elements, starting from either the beginning or end of the supplied range. search_n() can be thought of as a special case of search() or a general case of adjacent_find(): it finds the first sequence of n consecutive elements matching a supplied value. Finally, min_element() and max_element() find the minimum or maximum element in a sequence.
find_end() is the equivalent of search() that starts from the end of the sequence instead of the beginning. It is not the reverse equivalent of find(). There is no reverse equivalent of find(), find_if(), or other algorithms that search for a single element, because you can use a reverse_iterator to achieve the same effect. reverse_iterators are described in Chapter 23.
find(), adjacent_find(), min_element() quadratic time. All the algorithms use default vide overloaded versions that allow the client
, and max_element() run in linear time. The others run in comparisons of operator== or operator<, but also proto specify a comparison callback.
Here are examples of the preceding search algorithms:
#include <algorithm> #include <iostream> #include <vector> using namespace std;
int main(int argc, char** argv)
{
// The list of elements to be searched int elems[] = {5, 6, 9, 8, 8, 3};
633
Chapter 22
//Construct a vector from the list, exploiting the
//fact that pointers are iterators too. vector<int> myVector(elems, elems + 6); vector<int>::const_iterator it, it2;
//Find the min and max elements in the vector.
it = min_element(myVector.begin(), myVector.end()); it2 = max_element(myVector.begin(), myVector.end());
cout << “The min is “ << *it << “ and the max is “ << *it2 << endl;
// Find the first pair of matching consecutive elements. it = adjacent_find(myVector.begin(), myVector.end()); if (it != myVector.end()) {
cout << “Found two consecutive equal elements of value “ << *it << endl;
}
// Find the first of two values. int targets[] = {8, 9};
it = find_first_of(myVector.begin(), myVector.end(), targets, targets + 2);
if (it != myVector.end()) {
cout << “Found one of 8 or 9: “ << *it << endl;
}
// Find the first subsequence. int sub[] = {8, 3};
it = search(myVector.begin(), myVector.end(), sub, sub + 2); if (it != myVector.end()) {
cout << “Found subsequence 8, 3 at position “ << it - myVector.begin() << endl;
}
// Find the last subsequence (which should be the same as the first). it2 = find_end(myVector.begin(), myVector.end(), sub, sub + 2);
if (it != it2) {
cout << “Error: search and find_end found different subsequences “ << “ even though there is only one match.\n”;
}
// Find the first subsequence of two consecutive 8s. it = search_n(myVector.begin(), myVector.end(), 2, 8); if (it != myVector.end()) {
cout << “Found two consecutive 8s starting at position “ << it - myVector.begin() << endl;
}
return (0);
}
634
Mastering STL Algorithms and Function Objects
Here is the output:
The min is 3 and the max is 9
Found two consecutive equal elements of value 8
Found one of 8 or 9: 9
Found subsequence 8, 3 at position 4
Found two consecutive 8s starting at position 3
There are also several search algorithms that work only on sorted sequences: binary_search(), lower_bound(), upper_bound(), and equal_range(). binary_search() finds a matching element in logarithmic time. The other three are similar to their method equivalents on the map and set containers. See Chapter 21 and the Standard Library Reference resource on the Web site.
Remember to use equivalent container methods when available instead of the algorithms, because the methods are more efficient.
Numerical Processing Algorithms
You’ve seen an example of one numerical processing algorithm already: accumulate(). In addition, the count() and count_if() algorithms are useful for counting the number of elements of a given value in a container. They function similarly to the count() method on the map and set containers.
The other numerical processing algorithms are less useful, so they are not discussed here. See the Standard Library Reference resource on the Web site for details if you are interested.
Comparison Algorithms
You can compare entire ranges of elements in three different ways: equal(), mismatch(), and lexicographical_compare(). Each of the algorithms compares elements at parallel positions in the two ranges to each other in order. equal() returns true if all parallel elements are equal. mismatch() returns iterators referring into each range at the first point where parallel elements are unequal. lexicographical_compare() returns true if all the elements in the first range are less than their parallel elements in the second range, or if the first range is shorter than the second, and all elements up to that point are less than the parallel elements in the second range. You can think of this function as a generalization of alphabetization to noncharacter elements.
If you want to compare the elements of two containers of the same type, you can use operator== or operator< instead of equal() or lexicographical_compare(). The algorithms are useful primarily for comparing sequences of elements from different container types.
635
Chapter 22
Here are some examples of equal(), mismatch(), and lexicographical_compare():
#include <algorithm> #include <vector> #include <list> #include <iostream> using namespace std;
//Function template to populate a container of ints.
//The container must support push_back(). template<typename Container>
void populateContainer(Container& cont)
{
int num;
while (true) {
cout << “Enter a number (0 to quit): “; cin >> num;
if (num == 0) { break;
}
cont.push_back(num);
}
}
int main(int argc, char** argv)
{
vector<int> myVector; list<int> myList;
cout << “Populate the vector:\n”; populateContainer(myVector); cout << “Populate the list:\n”; populateContainer(myList);
if (myList.size() < myVector.size()) {
cout << “Sorry, the list is not long enough.\n”; return (0);
}
// Compare the two containers.
if (equal(myVector.begin(), myVector.end(), myList.begin())) { cout << “The two containers have equal elements\n”;
}else {
//If the containers were not equal, find out why not. pair<vector<int>::iterator, list<int>::iterator> miss =
mismatch(myVector.begin(), myVector.end(), myList.begin()); cout << “The first mismatch is at position “
<<miss.first - myVector.begin() << “. The vector has value “
<<*(miss.first) << “ and the list has value “ << *(miss.second)
<<endl;
}
// Now order them.
if (lexicographical_compare(myVector.begin(), myVector.end(), myList.begin(), myList.end())) {
636
Mastering STL Algorithms and Function Objects
cout << “The vector is lexicographically first.\n”; } else {
cout << “The list is lexicographically first.\n”;
}
return (0);
}
Here is a sample run of the program:
Populate the vector:
Enter a number (0 to quit): 5 Enter a number (0 to quit): 6 Enter a number (0 to quit): 7 Enter a number (0 to quit): 8 Enter a number (0 to quit): 0 Populate the list:
Enter a number (0 to quit): 5 Enter a number (0 to quit): 6 Enter a number (0 to quit): 7 Enter a number (0 to quit): 9 Enter a number (0 to quit): 0
The first mismatch is at position 3. The vector has value 8 and the list has value 9 The vector is lexicographically first.
Operational Algorithms
There is only one algorithm in this category: for_each(). However, it is one of the most useful algorithms in the STL. It executes a callback on each element of the range. You can use it with simple function callbacks for things like printing every element in a container. For example:
#include <algorithm> #include <map> #include <iostream> using namespace std;
void printPair(const pair<int, int>& elem)
{
cout << elem.first << “->” << elem.second << endl;
}
int main(int argc, char** argv)
{
map<int, int> myMap; myMap.insert(make_pair(4, 40)); myMap.insert(make_pair(5, 50)); myMap.insert(make_pair(6, 60)); myMap.insert(make_pair(7, 70)); myMap.insert(make_pair(8, 80));
for_each(myMap.begin(), myMap.end(), &printPair);
return (0);
}
637
Chapter 22
You can also perform much fancier tasks by using a functor to retain information between elements. for_each() returns a copy of the callback object, so you can accumulate information in your functor that you can retrieve after for_each() has finished processing each element. For example, you could calculate both the min and max elements in one pass by writing a functor that tracks both the minimum and maximum elements found so far. The MinAndMax functor shown in the following example assumes that the range on which it is called contains at least one element. It uses a Boolean first variable to initialize min and max to the first element, after which it compares each subsequent element to the currently stored min and max values.
#include <algorithm> #include <functional> #include <vector> #include <iostream> using namespace std;
//The populateContainer() function is identical to the one shown above for
//comparison alglorithms, so is omitted here.
class MinAndMax : public unary_function<int, void>
{
public:
MinAndMax();
void operator()(int elem);
// Make min and max public for easy access. int min, max;
protected: bool first;
};
MinAndMax::MinAndMax() : min(-1), max(-1), first(true)
{
}
void MinAndMax::operator()(int elem)
{
if (first) {
min = max = elem;
}else if (elem < min) { min = elem;
}else if (elem > max) { max = elem;
}
first = false;
}
int main(int argc, char** argv)
{
vector<int> myVector; populateContainer(myVector);
638