
- •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

The next two template functions automate the process of testing the various function object templates. There are two since the function objects are either unary or binary. In testUnary( ), you pass a source and destination vector, and a unary function object to apply to the source vector to produce the destination vector. In testBinary( ), there are two source vectors which are fed to a binary function to produce the destination vector. In both cases, the template functions simply turn around and call the transform( ) algorithm, although the tests could certainly be more complex.
For each test, you want to see a string describing what the test is, followed by the results of the test. To automate this, the preprocessor comes in handy; the T( ) and B( ) macros each take the expression you want to execute. They call that expression, then call print( ), passing it the result vector (they assume the expression changes a vector named r and br, respectively), and to produce the message the expression is “string-ized” using the preprocessor. So that way you see the code of the expression that is executed followed by the result vector.
The last little tool is a generator object that creates random bool values. To do this, it gets a random number from rand( ) and tests to see if it’s greater than RAND_MAX/2. If the random numbers are evenly distributed, this should happen half the time.
In main( ), three vector<int> are created: x and y for source values, and r for results. To initialize x and y with random values no greater than 50, a generator of type URandGen is used; this will be defined shortly. Since there is one operation where elements of x are divided by elements of y, we must ensure that there are no zero values of y. This is accomplished using the transform( ) algorithm, taking the source values from y and putting the results back into y. The function object for this is created with the expression:
bind2nd(plus<int>(), 1)
This uses the plus function object that adds two objects together. It is thus a binary function which requires two arguments; we only want to pass it one argument (the element from y) and have the other argument be the value 1. A “binder” does the trick (we will look at these next). The binder in this case says “make a new function object which is the plus function object with the second argument fixed at 1.”
Another of the tests in the program compares the elements in the two vectors for equality, so it is interesting to guarantee that at least one pair of elements is equivalent; in this case element zero is chosen.
Once the two vectors are printed, T( ) is used to test each of the function objects that produces a numerical value, and then B( ) is used to test each function object that produces a Boolean result. The result is placed into a vector<bool>, and when this vector is printed it produces a ‘1’ for a true value and a ‘0’ for a false value.
Binders
It’s common to want to take a binary function object and to “bind” one of its arguments to a constant value. After binding, you get a unary function object.
Chapter 15: Multiple Inheritance
269

For example, suppose you want to find integers that are less than a particular value, say 20. Sensibly enough, the STL algorithms have a function called find_if( ) that will search through a sequence; however, find_if( ) requires a unary predicate to tell it if this is what you’re looking for. This unary predicate can of course be some function object that you have written by hand, but it can also be created using the built-in function object templates. In this case, the less template will work, but that produces a binary predicate, so we need some way of forming a unary predicate. The binder templates (which work with any binary function object, not just binary predicates) give you two choices:
bind1st(const BinaryFunction& op, const T& t); bind2nd(const BinaryFunction& op, const T& t);
Both bind t to one of the arguments of op, but bind1st( ) binds t to the first argument, and bind2nd( ) binds t to the second argument. With less, the function object that provides the solution to our exercise is:
bind2nd(less<int>(), 20);
This produces a new function object that returns true if its argument is less than 20. Here it is, used with find_if( ):
//: C05:Binder1.cpp
// Using STL "binders" #include "Generators.h" #include "copy_if.h" #include <algorithm> #include <vector> #include <iostream> #include <functional> using namespace std;
int main() {
const int sz = 10; const int max = 40; vector<int> a(sz), r; URandGen urg(max);
ostream_iterator<int> out(cout, " "); generate_n(a.begin(), sz, urg); copy(a.begin(), a.end(), out);
int* d = find_if(a.begin(), a.end(), bind2nd(less<int>(), 20));
cout << "\n *d = " << *d << endl;
//copy_if() is not in the Standard C++ library
//but is defined later in the chapter:
copy_if(a.begin(), a.end(), back_inserter(r), bind2nd(less<int>(), 20));
Chapter 15: Multiple Inheritance
270

copy(r.begin(), r.end(), out); cout << endl;
} ///:~
The vector<int> a is filled with random numbers between 0 and max. find_if( ) finds the first element in a that satisfies the predicate (that is, which is less than 20) and returns an iterator to it (here, the type of the iterator is actually just int* although I could have been more precise and said vector<int>::iterator instead).
A more interesting algorithm to use is copy_if( ), which isn’t part of the STL but is defined at the end of this chapter. This algorithm only copies an element from the source to the destination if that element satisfies a predicate. So the resulting vector will only contain elements that are less than 20.
Here’s a second example, using a vector<string> and replacing strings that satisfy particular conditions:
//: C05:Binder2.cpp // More binders #include <algorithm> #include <vector> #include <string> #include <iostream> #include <functional> using namespace std;
int main() {
ostream_iterator<string> out(cout, " "); vector<string> v, r;
v.push_back("Hi"); v.push_back("Hi"); v.push_back("Hey"); v.push_back("Hee"); v.push_back("Hi"); copy(v.begin(), v.end(), out); cout << endl;
//Replace each "Hi" with "Ho": replace_copy_if(v.begin(), v.end(),
back_inserter(r), bind2nd(equal_to<string>(), "Hi"), "Ho");
copy(r.begin(), r.end(), out); cout << endl;
//Replace anything that's not "Hi" with "Ho": replace_if(v.begin(), v.end(),
not1(bind2nd(equal_to<string>(),"Hi")),"Ho");
Chapter 15: Multiple Inheritance
271

copy(v.begin(), v.end(), out); cout << endl;
} ///:~
This uses another pair of STL algorithms. The first, replace_copy_if( ), copies each element from a source range to a destination range, performing replacements on those that satisfy a particular unary predicate. The second, replace_if( ), doesn’t do any copying but instead performs the replacements directly into the original range.
A binder doesn’t have to produce a unary predicate; it can also create a unary function (that is, a function that returns something other than bool). For example, suppose you’d like to multiply every element in a vector by 10. Using a binder with the transform( ) algorithm does the trick:
//: C05:Binder3.cpp
// Binders aren't limited to producing predicates #include "Generators.h"
#include <algorithm> #include <vector> #include <iostream> #include <functional> using namespace std;
int main() {
ostream_iterator<int> out(cout, " "); vector<int> v(15);
generate(v.begin(), v.end(), URandGen(20)); copy(v.begin(), v.end(), out);
cout << endl;
transform(v.begin(), v.end(), v.begin(), bind2nd(multiplies<int>(), 10));
copy(v.begin(), v.end(), out); cout << endl;
} ///:~
Since the third argument to transform( ) is the same as the first, the resulting elements are copied back into the source vector. The function object created by bind2nd( ) in this case produces an int result.
The “bound” argument to a binder cannot be a function object, but it does not have to be a compile-time constant. For example:
//: C05:Binder4.cpp
//The bound argument does not have
//to be a compile-time constant #include "copy_if.h"
Chapter 15: Multiple Inheritance
272