- •Thinking in C++ 2nd edition Volume 2: Standard Libraries & Advanced Topics
- •Preface
- •What’s new in the second edition
- •What’s in Volume 2 of this book
- •How to get Volume 2
- •Prerequisites
- •Learning C++
- •Goals
- •Chapters
- •Exercises
- •Exercise solutions
- •Source code
- •Language standards
- •Language support
- •The book’s CD ROM
- •Seminars, CD Roms & consulting
- •Errors
- •Acknowledgements
- •Library overview
- •1: Strings
- •What’s in a string
- •Creating and initializing C++ strings
- •Initialization limitations
- •Operating on strings
- •Appending, inserting and concatenating strings
- •Replacing string characters
- •Concatenation using non-member overloaded operators
- •Searching in strings
- •Finding in reverse
- •Finding first/last of a set
- •Removing characters from strings
- •Stripping HTML tags
- •Comparing strings
- •Using iterators
- •Iterating in reverse
- •Strings and character traits
- •A string application
- •Summary
- •Exercises
- •2: Iostreams
- •Why iostreams?
- •True wrapping
- •Iostreams to the rescue
- •Sneak preview of operator overloading
- •Inserters and extractors
- •Manipulators
- •Common usage
- •Line-oriented input
- •Overloaded versions of get( )
- •Reading raw bytes
- •Error handling
- •File iostreams
- •Open modes
- •Iostream buffering
- •Seeking in iostreams
- •Creating read/write files
- •User-allocated storage
- •Output strstreams
- •Automatic storage allocation
- •Proving movement
- •A better way
- •Output stream formatting
- •Internal formatting data
- •Format fields
- •Width, fill and precision
- •An exhaustive example
- •Formatting manipulators
- •Manipulators with arguments
- •Creating manipulators
- •Effectors
- •Iostream examples
- •Code generation
- •Maintaining class library source
- •Detecting compiler errors
- •A simple datalogger
- •Generating test data
- •Verifying & viewing the data
- •Counting editor
- •Breaking up big files
- •Summary
- •Exercises
- •3: Templates in depth
- •Nontype template arguments
- •Typedefing a typename
- •Using typename instead of class
- •Function templates
- •A string conversion system
- •A memory allocation system
- •Type induction in function templates
- •Taking the address of a generated function template
- •Local classes in templates
- •Applying a function to an STL sequence
- •Template-templates
- •Member function templates
- •Why virtual member template functions are disallowed
- •Nested template classes
- •Template specializations
- •A practical example
- •Pointer specialization
- •Partial ordering of function templates
- •Design & efficiency
- •Preventing template bloat
- •Explicit instantiation
- •Explicit specification of template functions
- •Controlling template instantiation
- •Template programming idioms
- •Summary
- •Containers and iterators
- •STL reference documentation
- •The Standard Template Library
- •The basic concepts
- •Containers of strings
- •Inheriting from STL containers
- •A plethora of iterators
- •Iterators in reversible containers
- •Iterator categories
- •Input: read-only, one pass
- •Output: write-only, one pass
- •Forward: multiple read/write
- •Bidirectional: operator--
- •Random-access: like a pointer
- •Is this really important?
- •Predefined iterators
- •IO stream iterators
- •Manipulating raw storage
- •Basic sequences: vector, list & deque
- •Basic sequence operations
- •vector
- •Cost of overflowing allocated storage
- •Inserting and erasing elements
- •deque
- •Converting between sequences
- •Cost of overflowing allocated storage
- •Checked random-access
- •list
- •Special list operations
- •list vs. set
- •Swapping all basic sequences
- •Robustness of lists
- •Performance comparison
- •A completely reusable tokenizer
- •stack
- •queue
- •Priority queues
- •Holding bits
- •bitset<n>
- •vector<bool>
- •Associative containers
- •Generators and fillers for associative containers
- •The magic of maps
- •A command-line argument tool
- •Multimaps and duplicate keys
- •Multisets
- •Combining STL containers
- •Creating your own containers
- •Summary
- •Exercises
- •5: STL Algorithms
- •Function objects
- •Classification of function objects
- •Automatic creation of function objects
- •Binders
- •Function pointer adapters
- •SGI extensions
- •A catalog of STL algorithms
- •Support tools for example creation
- •Filling & generating
- •Example
- •Counting
- •Example
- •Manipulating sequences
- •Example
- •Searching & replacing
- •Example
- •Comparing ranges
- •Example
- •Removing elements
- •Example
- •Sorting and operations on sorted ranges
- •Sorting
- •Example
- •Locating elements in sorted ranges
- •Example
- •Merging sorted ranges
- •Example
- •Set operations on sorted ranges
- •Example
- •Heap operations
- •Applying an operation to each element in a range
- •Examples
- •Numeric algorithms
- •Example
- •General utilities
- •Creating your own STL-style algorithms
- •Summary
- •Exercises
- •Perspective
- •Duplicate subobjects
- •Ambiguous upcasting
- •virtual base classes
- •The "most derived" class and virtual base initialization
- •"Tying off" virtual bases with a default constructor
- •Overhead
- •Upcasting
- •Persistence
- •MI-based persistence
- •Improved persistence
- •Avoiding MI
- •Mixin types
- •Repairing an interface
- •Summary
- •Exercises
- •7: Exception handling
- •Error handling in C
- •Throwing an exception
- •Catching an exception
- •The try block
- •Exception handlers
- •Termination vs. resumption
- •The exception specification
- •Better exception specifications?
- •Catching any exception
- •Rethrowing an exception
- •Uncaught exceptions
- •Function-level try blocks
- •Cleaning up
- •Constructors
- •Making everything an object
- •Exception matching
- •Standard exceptions
- •Programming with exceptions
- •When to avoid exceptions
- •Not for asynchronous events
- •Not for ordinary error conditions
- •Not for flow-of-control
- •You’re not forced to use exceptions
- •New exceptions, old code
- •Typical uses of exceptions
- •Always use exception specifications
- •Start with standard exceptions
- •Nest your own exceptions
- •Use exception hierarchies
- •Multiple inheritance
- •Catch by reference, not by value
- •Throw exceptions in constructors
- •Don’t cause exceptions in destructors
- •Avoid naked pointers
- •Overhead
- •Summary
- •Exercises
- •8: Run-time type identification
- •The “Shape” example
- •What is RTTI?
- •Two syntaxes for RTTI
- •Syntax specifics
- •Producing the proper type name
- •Nonpolymorphic types
- •Casting to intermediate levels
- •void pointers
- •Using RTTI with templates
- •References
- •Exceptions
- •Multiple inheritance
- •Sensible uses for RTTI
- •Revisiting the trash recycler
- •Mechanism & overhead of RTTI
- •Creating your own RTTI
- •Explicit cast syntax
- •Summary
- •Exercises
- •9: Building stable systems
- •Shared objects & reference counting
- •Reference-counted class hierarchies
- •Finding memory leaks
- •An extended canonical form
- •Exercises
- •10: Design patterns
- •The pattern concept
- •The singleton
- •Variations on singleton
- •Classifying patterns
- •Features, idioms, patterns
- •Basic complexity hiding
- •Factories: encapsulating object creation
- •Polymorphic factories
- •Abstract factories
- •Virtual constructors
- •Destructor operation
- •Callbacks
- •Observer
- •The “interface” idiom
- •The “inner class” idiom
- •The observer example
- •Multiple dispatching
- •Visitor, a type of multiple dispatching
- •Efficiency
- •Flyweight
- •The composite
- •Evolving a design: the trash recycler
- •Improving the design
- •“Make more objects”
- •A pattern for prototyping creation
- •Trash subclasses
- •Parsing Trash from an external file
- •Recycling with prototyping
- •Abstracting usage
- •Applying double dispatching
- •Implementing the double dispatch
- •Applying the visitor pattern
- •More coupling?
- •RTTI considered harmful?
- •Summary
- •Exercises
- •11: Tools & topics
- •The code extractor
- •Debugging
- •Trace macros
- •Trace file
- •Abstract base class for debugging
- •Tracking new/delete & malloc/free
- •CGI programming in C++
- •Encoding data for CGI
- •The CGI parser
- •Testing the CGI parser
- •Using POST
- •Handling mailing lists
- •Maintaining your list
- •Mailing to your list
- •A general information-extraction CGI program
- •Parsing the data files
- •Summary
- •Exercises
- •General C++
- •My own list of books
- •Depth & dark corners
- •Design Patterns
- •Index
produces a binary function object which compares its first and second arguments:
return first > second;
and returns a bool. But we don’t want a binary predicate, and we want to compare against the constant value “b.” So bind2nd( ) says: create a new function object which only takes one argument, by taking this greater<NString>( ) function and forcing the second argument to always be “b.” The first argument (the only argument) will be the one from the vector ns.
You’ll see from the output that with the unstable partition, the objects are correctly above and below the partition point, but in no particular order, whereas with the stable partition their original order is maintained.
Searching & replacing
All of these algorithms are used for searching for one or more objects within a range defined by the first two iterator arguments.
InputIterator find(InputIterator first, InputIterator last, const EqualityComparable& value);
Searches for value within a range of elements. Returns an iterator in the range [first, last) that points to the first occurrence of value. If value isn’t in the range, then find( ) returns last. This is a linear search, that is, it starts at the beginning and looks at each sequential element without making any assumptions about the way the elements are ordered. In contrast, a binary_search( ) (defined later) works on a sorted sequence and can thus be much faster.
InputIterator find_if(InputIterator first, InputIterator last, Predicate pred);
Just like find( ), find_if( ) performs a linear search through the range. However, instead of searching for value, find_if( ) looks for an element such that the Predicate pred returns true when applied to that element. Returns last if no such element can be found.
ForwardIterator adjacent_find(ForwardIterator first, ForwardIterator last); ForwardIterator adjacent_find(ForwardIterator first, ForwardIterator last,
BinaryPredicate binary_pred);
Like find( ), performs a linear search through the range, but instead of looking for only one element it searches for two elements that are right next to each other. The first form of the function looks for two elements that are equivalent (via operator==). The second form looks for two adjacent elements that, when passed together to binary_pred, produce a true result. If two adjacent elements cannot be found, last is returned.
ForwardIterator1 find_first_of(ForwardIterator1 first1, ForwardIterator1 last1, ForwardIterator2 first2, ForwardIterator2 last2);
ForwardIterator1 find_first_of(ForwardIterator1 first1, ForwardIterator1 last1, ForwardIterator2 first2, ForwardIterator2 last2, BinaryPredicate binary_pred);
Chapter 15: Multiple Inheritance
299
Like find( ), performs a linear search through the range. The first form finds the first element in the first range that is equivalent to any of the elements in the second range. The second form finds the first element in the first range that produces true when passed to binary_pred along with any of the elements in the second range. When a BinaryPredicate is used with two ranges in the algorithms, the element from the first range becomes the first argument to binary_pred, and the element from the second range becomes the second argument.
ForwardIterator1 search(ForwardIterator1 first1, ForwardIterator1 last1, ForwardIterator2 first2, ForwardIterator2 last2);
ForwardIterator1 search(ForwardIterator1 first1, ForwardIterator1 last1, ForwardIterator2 first2, ForwardIterator2 last2 BinaryPredicate binary_pred);
Attempts to find the entire range [first2, last2) within the range [first1, last1). That is, it checks to see if the second range occurs (in the exact order of the second range) within the first range, and if so returns an iterator pointing to the place in the first range where the second range begins. Returns last1 if no subset can be found. The first form performs its test using operator==, while the second checks to see if each pair of objects being compared causes binary_pred to return true.
ForwardIterator1 find_end(ForwardIterator1 first1, ForwardIterator1 last1, ForwardIterator2 first2, ForwardIterator2 last2);
ForwardIterator1 find_end(ForwardIterator1 first1, ForwardIterator1 last1, ForwardIterator2 first2, ForwardIterator2 last2, BinaryPredicate binary_pred);
The forms and arguments are just like search( ) in that it looks for the second range within the first range, but while search( ) looks for the first occurrence of the second range, find_end( ) looks for the last occurrence of the second range within the first.
ForwardIterator search_n(ForwardIterator first, ForwardIterator last, Size count, const T& value);
ForwardIterator search_n(ForwardIterator first, ForwardIterator last, Size count, const T& value, BinaryPredicate binary_pred);
Looks for a group of count consecutive values in [first, last) that are all equal to value (in the first form) or that all cause a return value of true when passed into binary_pred along with value (in the second form). Returns last if such a group cannot be found.
ForwardIterator min_element(ForwardIterator first, ForwardIterator last); ForwardIterator min_element(ForwardIterator first, ForwardIterator last, BinaryPredicate binary_pred);
Returns an iterator pointing to the first occurrence of the smallest value in the range (there may be multiple occurrences of the smallest value). Returns last if the range is empty. The first version performs comparisons with operator< and the value r returned is such that *e < *r
is false for every element e in the range. The second version compares using binary_pred and the value r returned is such that binary_pred (*e, *r) is false for every element e in the range.
Chapter 15: Multiple Inheritance
300
ForwardIterator max_element(ForwardIterator first, ForwardIterator last); ForwardIterator max_element(ForwardIterator first, ForwardIterator last, BinaryPredicate binary_pred);
Returns an iterator pointing to the first occurrence of the largest value in the range (there may be multiple occurrences of the largest value). Returns last if the range is empty. The first version performs comparisons with operator< and the value r returned is such that
*r < *e
is false for every element e in the range. The second version compares using binary_pred and the value r returned is such that binary_pred (*r, *e) is false for every element e in the range.
void replace(ForwardIterator first, ForwardIterator last, const T& old_value, const T& new_value);
void replace_if(ForwardIterator first, ForwardIterator last, Predicate pred, const T& new_value);
OutputIterator replace_copy(InputIterator first, InputIterator last, OutputIterator result, const T& old_value, const T& new_value);
OutputIterator replace_copy_if(InputIterator first, InputIterator last, OutputIterator result, Predicate pred, const T& new_value);
Each of the “replace” forms moves through the range [first, last), finding values that match a criterion and replacing them with new_value. Both replace( ) and replace_copy( ) simply look for old_value to replace, while replace_if( ) and replace_copy_if( ) look for values that satisfy the predicate pred. The “copy” versions of the functions do not modify the original range but instead make a copy with the replacements into result (incrementing result after each assignment).
Example
To provide easy viewing of the results, this example will manipulate vectors of int. Again, not every possible version of each algorithm will be shown (some that should be obvious have been omitted).
//: C05:SearchReplace.cpp
// The STL search and replace algorithms #include "PrintSequence.h"
#include <vector> #include <algorithm> #include <functional> using namespace std;
struct PlusOne {
bool operator()(int i, int j) { return j == i + 1;
}
Chapter 15: Multiple Inheritance
301
};
class MulMoreThan { int value;
public:
MulMoreThan(int val) : value(val) {} bool operator()(int v, int m) {
return v * m > value;
}
};
int main() {
int a[] = { 1, 2, 3, 4, 5, 6, 6, 7, 7, 7, 8, 8, 8, 8, 11, 11, 11, 11, 11 };
const int asz = sizeof a / sizeof *a; vector<int> v(a, a + asz);
print(v, "v", " "); vector<int>::iterator it =
find(v.begin(), v.end(), 4); cout << "find: " << *it << endl; it = find_if(v.begin(), v.end(), bind2nd(greater<int>(), 8));
cout << "find_if: " << *it << endl;
it = adjacent_find(v.begin(), v.end()); while(it != v.end()) {
cout << "adjacent_find: " << *it << ", " << *(it + 1) << endl;
it = adjacent_find(it + 2, v.end());
}
it = adjacent_find(v.begin(), v.end(), PlusOne());
while(it != v.end()) {
cout << "adjacent_find PlusOne: " << *it << ", " << *(it + 1) << endl;
it = adjacent_find(it + 1, v.end(), PlusOne());
}
int b[] = { 8, 11 };
const int bsz = sizeof b / sizeof *b; print(b, b + bsz, "b", " ");
it = find_first_of(v.begin(), v.end(), b, b + bsz);
print(it, it + bsz, "find_first_of", " ");
Chapter 15: Multiple Inheritance
302
it = find_first_of(v.begin(), v.end(), b, b + bsz, PlusOne());
print(it,it + bsz,"find_first_of PlusOne"," "); it = search(v.begin(), v.end(), b, b + bsz); print(it, it + bsz, "search", " ");
int c[] = { 5, 6, 7 };
const int csz = sizeof c / sizeof *c; print(c, c + csz, "c", " ");
it = search(v.begin(), v.end(), c, c + csz, PlusOne());
print(it, it + csz,"search PlusOne", " "); int d[] = { 11, 11, 11 };
const int dsz = sizeof d / sizeof *d; print(d, d + dsz, "d", " ");
it = find_end(v.begin(), v.end(), d, d + dsz); print(it, v.end(),"find_end", " ");
int e[] = { 9, 9 }; print(e, e + 2, "e", " ");
it = find_end(v.begin(), v.end(), e, e + 2, PlusOne());
print(it, v.end(),"find_end PlusOne"," "); it = search_n(v.begin(), v.end(), 3, 7); print(it, it + 3, "search_n 3, 7", " "); it = search_n(v.begin(), v.end(),
6, 15, MulMoreThan(100)); print(it, it + 6,
"search_n 6, 15, MulMoreThan(100)", " "); cout << "min_element: " <<
*min_element(v.begin(), v.end()) << endl; cout << "max_element: " <<
*max_element(v.begin(), v.end()) << endl; vector<int> v2;
replace_copy(v.begin(), v.end(), back_inserter(v2), 8, 47);
print(v2, "replace_copy 8 -> 47", " "); replace_if(v.begin(), v.end(),
bind2nd(greater_equal<int>(), 7), -1); print(v, "replace_if >= 7 -> -1", " ");
} ///:~
The example begins with two predicates: PlusOne which is a binary predicate that returns true if the second argument is equivalent to one plus the first argument, and MulMoreThan which returns true if the first argument times the second argument is greater than a value stored in the object. These binary predicates are used as tests in the example.
Chapter 15: Multiple Inheritance
303
In main( ), an array a is created and fed to the constructor for vector<int> v. This vector will be used as the target for the search and replace activities, and you’ll note that there are duplicate elements – these will be discovered by some of the search/replace routines.
The first test demonstrates find( ), discovering the value 4 in v. The return value is the iterator pointing to the first instance of 4, or the end of the input range (v.end( )) if the search value is not found.
find_if( ) uses a predicate to determine if it has discovered the correct element. In the above example, this predicate is created on the fly using greater<int> (that is, “see if the first int argument is greater than the second”) and bind2nd( ) to fix the second argument to 8. Thus, it returns true if the value in v is greater than 8.
Since there are a number of cases in v where two identical objects appear next to each other, the test of adjacent_find( ) is designed to find them all. It starts looking from the beginning and then drops into a while loop, making sure that the iterator it has not reached the end of the input sequence (which would mean that no more matches can be found). For each match it finds, the loop prints out the matches and then performs the next adjacent_find( ), this time using it + 2 as the first argument (this way, it moves past the two elements that it already found).
You might look at the while loop and think that you can do it a bit more cleverly, to wit:
while(it != v.end()) {
cout << "adjacent_find: " << *it++ << ", " << *it++ << endl;
it = adjacent_find(it, v.end());
}
Of course, this is exactly what I tried at first. However, I did not get the output I expected, on any compiler. This is because there is no guarantee about when the increments occur in the above expression. A bit of a disturbing discovery, I know, but the situation is best avoided now that you’re aware of it.
The next test uses adjacent_find( ) with the PlusOne predicate, which discovers all the places where the next number in the sequence v changes from the previous by one. The same while approach is used to find all the cases.
find_first_of( ) requires a second range of objects for which to hunt; this is provided in the array b. Notice that, because the first range and the second range in find_first_of( ) are controlled by separate template arguments, those ranges can refer to two different types of containers, as seen here. The second form of find_first_of( ) is also tested, using PlusOne.
search( ) finds exactly the second range inside the first one, with the elements in the same order. The second form of search( ) uses a predicate, which is typically just something that defines equivalence, but it also opens some interesting possibilities – here, the PlusOne predicate causes the range { 4, 5, 6 } to be found.
Chapter 15: Multiple Inheritance
304