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

// before any code that uses pvals is called: pvals.parse(argc, argv, usage); pvals.print();
Animal a;
cout << "Animal a values:" << endl; a.print();
} ///:~
This program can create Animal objects with different characteristics, and those characteristics can be established with the command line. The default characteristics are given in the two-dimensional array of char* called defaults and, after the usage string you can see a global instance of ProgVals called pvals is created; this is important because it allows the rest of the code in the program to access the values.
Note that Animal’s default constructor uses the values in pvals inside its constructor initializer list. When you run the program you can try creating different animal characteristics.
Many command-line programs also use a style of beginning a flag with a hyphen, and sometimes they use single-character flags.
The STL map is used in numerous places throughout the rest of this book.
Multimaps and duplicate keys
A multimap is a map that can contain duplicate keys. At first this may seem like a strange idea, but it can occur surprisingly often. A phone book, for example, can have many entries with the same name.
Suppose you are monitoring wildlife, and you want to keep track of where and when each type of animal is spotted. Thus, you may see many animals of the same kind, all in different locations and at different times. So if the type of animal is the key, you’ll need a multimap. Here’s what it looks like:
//: C04:WildLifeMonitor.cpp #include <vector>
#include <map> #include <string> #include <algorithm> #include <iostream> #include <sstream> #include <ctime> using namespace std;
class DataPoint {
int x, y; // Location coordinates time_t time; // Time of Sighting
Chapter 15: Multiple Inheritance
244

public:
DataPoint() : x(0), y(0), time(0) {} DataPoint(int xx, int yy, time_t tm) :
x(xx), y(yy), time(tm) {}
// Synthesized operator=, copy-constructor OK int getX() { return x; }
int getY() { return y; }
time_t* getTime() { return &time; }
};
string animal[] = {
"chipmunk", "beaver", "marmot", "weasel", "squirrel", "ptarmigan", "bear", "eagle", "hawk", "vole", "deer", "otter", "hummingbird",
};
const int asz = sizeof animal/sizeof *animal; vector<string> animals(animal, animal + asz);
//All the information is contained in a
//"Sighting," which can be sent to an ostream: typedef pair<string, DataPoint> Sighting;
ostream&
operator<<(ostream& os, const Sighting& s) { return os << s.first << " sighted at x= " <<
s.second.getX() << ", y= " << s.second.getY() << ", time = " << ctime(s.second.getTime());
}
// A generator for Sightings: class SightingGen {
vector<string>& animals; static const int d = 100;
public:
SightingGen(vector<string>& an) : animals(an) { srand(time(0)); }
Sighting operator()() { Sighting result;
int select = rand() % animals.size(); result.first = animals[select]; result.second = DataPoint(
rand() % d, rand() % d, time(0)); return result;
Chapter 15: Multiple Inheritance
245

}
};
typedef multimap<string, DataPoint> DataMap; typedef DataMap::iterator DMIter;
int main() {
DataMap sightings; generate_n(
inserter(sightings, sightings.begin()), 50, SightingGen(animals));
//Print everything: copy(sightings.begin(), sightings.end(),
ostream_iterator<Sighting>(cout, ""));
//Print sightings for selected animal: while(true) {
cout << "select an animal or 'q' to quit: "; for(int i = 0; i < animals.size(); i++)
cout <<'['<< i <<']'<< animals[i] << ' '; cout << endl;
string reply; cin >> reply;
if(reply.at(0) == 'q') return 0; istringstream r(reply);
int i;
r >> i; // Converts to int i %= animals.size();
//Iterators in "range" denote begin, one
//past end of matching range: pair<DMIter, DMIter> range =
sightings.equal_range(animals[i]); copy(range.first, range.second,
ostream_iterator<Sighting>(cout, ""));
}
} ///:~
All the data about a sighting is encapsulated into the class DataPoint, which is simple enough that it can rely on the synthesized assignment and copy-constructor. It uses the Standard C library time functions to record the time of the sighting.
In the array of string animal, notice that the char* constructor is automatically used during initialization, which makes initializing an array of string quite convenient. Since it’s easier to use the animal names in a vector, the length of the array is calculated and a vector<string> is initialized using the vector(iterator, iterator) constructor.
Chapter 15: Multiple Inheritance
246

The key-value pairs that make up a Sighting are the string which names the type of animal, and the DataPoint that says where and when it was sighted. The standard pair template combines these two types and is typedefed to produce the Sighting type. Then an ostream operator<< is created for Sighting; this will allow you to iterate through a map or multimap of Sightings and print it out.
SightingGen generates random sightings at random data points to use for testing. It has the usual operator( ) necessary for a function object, but it also has a constructor to capture and store a reference to a vector<string>, which is where the aforementioned animal names are stored.
A DataMap is a multimap of string-DataPoint pairs, which means it stores Sightings. It is filled with 50 Sightings using generate_n( ), and printed out (notice that because there is an operator<< that takes a Sighting, an ostream_iterator can be created). At this point the user is asked to select the animal that they want to see all the sightings for. If you press ‘q’ the program will quit, but if you select an animal number, then the equal_range( ) member function is invoked. This returns an iterator (DMIter) to the beginning of the set of matching pairs, and one indicating past-the-end of the set. Since only one object can be returned from a function, equal_range( ) makes use of pair. Since the range pair has the beginning and ending iterators of the matching set, those iterators can be used in copy( ) to print out all the sightings for a particular type of animal.
Multisets
You’ve seen the set, which only allows one object of each value to be inserted. The multiset is odd by comparison since it allows more than one object of each value to be inserted. This seems to go against the whole idea of “setness,” where you can ask “is ‘it’ in this set?” If there can be more than one of ‘it’, then what does that question mean?
With some thought, you can see that it makes no sense to have more than one object of the same value in a set if those duplicate objects are exactly the same (with the possible exception of counting occurrences of objects, but as seen earlier in this chapter that can be handled in an alternative, more elegant fashion). Thus each duplicate object will have something that makes it unique from the other duplicates – most likely different state information that is not used in the calculation of the value during the comparison. That is, to the comparison operation, the objects look the same but they actually contain some differing internal state.
Like any STL container that must order its elements, the multiset template uses the less template by default to determine element ordering. This uses the contained classes’ operator<, but you may of course substitute your own comparison function.
Consider a simple class that contains one element that is used in the comparison, and another that is not:
//: C04:MultiSet1.cpp
// Demonstration of multiset behavior #include <iostream>
Chapter 15: Multiple Inheritance
247

#include <set> #include <algorithm> #include <ctime> using namespace std;
class X {
char c; // Used in comparison int i; // Not used in comparison
//Don't need default constructor and operator= X();
X& operator=(const X&);
//Usually need a copy-constructor (but the
//synthesized version works here)
public:
X(char cc, int ii) : c(cc), i(ii) {} // Notice no operator== is required
friend bool operator<(const X& x, const X& y) { return x.c < y.c;
}
friend ostream& operator<<(ostream& os, X x) { return os << x.c << ":" << x.i;
}
};
class Xgen { static int i;
// Number of characters to select from: static const int span = 6;
public:
Xgen() { srand(time(0)); } X operator()() {
char c = 'A' + rand() % span; return X(c, i++);
}
};
int Xgen::i = 0;
typedef multiset<X> Xmset;
typedef Xmset::const_iterator Xmit;
int main() { Xmset mset;
Chapter 15: Multiple Inheritance
248

// Fill it with X's: generate_n(inserter(mset, mset.begin()),
25, Xgen());
//Initialize a regular set from mset: set<X> unique(mset.begin(), mset.end()); copy(unique.begin(), unique.end(),
ostream_iterator<X>(cout, " ")); cout << "\n----\n";
//Iterate over the unique values: for(set<X>::iterator i = unique.begin();
i!= unique.end(); i++) {
pair<Xmit, Xmit> p = mset.equal_range(*i); copy(p.first, p.second,
ostream_iterator<X>(cout, " ")); cout << endl;
}
} ///:~
In X, all the comparisons are made with the char c. The comparison is performed with operator<, which is all that is necessary for the multiset, since in this example the default less comparison object is used. The class Xgen is used to randomly generate X objects, but the comparison value is restricted to the span from ‘A’ to ‘E’. In main( ), a multiset<X> is created and filled with 25 X objects using Xgen, guaranteeing that there will be duplicate keys. So that we know what the unique values are, a regular set<X> is created from the multiset (using the iterator, iterator constructor). These values are displayed, then each one is used to produce the equal_range( ) in the multiset (equal_range( ) has the same meaning here as it does with multimap: all the elements with matching keys). Each set of matching keys is then printed.
As a second example, a (possibly) more elegant version of WordCount.cpp can be created using multiset:
//: C04:MultiSetWordCount.cpp //{L} StreamTokenizer
// Count |
occurrences of words using a multiset |
#include |
"StreamTokenizer.h" |
#include |
"../require.h" |
#include |
<string> |
#include |
<set> |
#include |
<fstream> |
#include |
<iterator> |
using namespace std;
int main(int argc, char* argv[]) { requireArgs(argc, 1);
Chapter 15: Multiple Inheritance
249