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

ostream_iterator<pair<int,int> >(cout,"\n")); } ///:~
The second argument to inserter is an iterator, which actually isn’t used in the case of associative containers since they maintain their order internally, rather than allowing you to tell them where the element should be inserted. However, an insert_iterator can be used with many different types of containers so you must provide the iterator.
Note how the ostream_iterator is created to output a pair; this wouldn’t have worked if the operator<< hadn’t been created, and since it’s a template it is automatically instantiated for pair<int, int>.
The magic of maps
An ordinary array uses an integral value to index into a sequential set of elements of some type. A map is an associative array, which means you associate one object with another in an array-like fashion, but instead of selecting an array element with a number as you do with an ordinary array, you look it up with an object! The example which follows counts the words in a text file, so the index is the string object representing the word, and the value being looked up is the object that keeps count of the strings.
In a single-item container like a vector or list, there’s only one thing being held. But in a map, you’ve got two things: the key (what you look up by, as in mapname[key]) and the value that results from the lookup with the key. If you simply want to move through the entire map and list each key-value pair, you use an iterator, which when dereferenced produces a pair object containing both the key and the value. You access the members of a pair by selecting first or second.
This same philosophy of packaging two items together is also used to insert elements into the map, but the pair is created as part of the instantiated map and is called value_type, containing the key and the value. So one option for inserting a new element is to create a value_type object, loading it with the appropriate objects and then calling the insert( ) member function for the map. Instead, the following example makes use of the aforementioned special feature of map: if you’re trying to find an object by passing in a key to operator[ ] and that object doesn’t exist, operator[ ] will automatically insert a new keyvalue pair for you, using the default constructor for the value object. With that in mind, consider an implementation of a word counting program:
//: C04:WordCount.cpp //{L} StreamTokenizer
// Count occurrences of words using a map #include "StreamTokenizer.h"
#include "../require.h" #include <string> #include <map>
#include <iostream>
Chapter 15: Multiple Inheritance
239

#include <fstream> using namespace std;
class Count { int i;
public:
Count() : i(0) {}
void operator++(int) { i++; } // Post-increment int& val() { return i; }
};
typedef map<string, Count> WordMap; typedef WordMap::iterator WMIter;
int main(int argc, char* argv[]) { requireArgs(argc, 1);
ifstream in(argv[1]); assure(in, argv[1]); StreamTokenizer words(in); WordMap wordmap;
string word;
while((word = words.next()).size() != 0) wordmap[word]++;
for(WMIter w = wordmap.begin(); w != wordmap.end(); w++)
cout << (*w).first << ": "
<<(*w).second.val() << endl;
}///:~
The need for the Count class is to contain an int that’s automatically initialized to zero. This is necessary because of the crucial line:
wordmap[word]++;
This finds the word that has been produced by StreamTokenizer and increments the Count object associated with that word, which is fine as long as there is a key-value pair for that string. If there isn’t, the map automatically inserts a key for the word you’re looking up, and a Count object, which is initialized to zero by the default constructor. Thus, when it’s incremented the Count becomes 1.
Printing the entire list requires traversing it with an iterator (there’s no copy( ) shortcut for a map unless you want to write an operator<< for the pair in the map). As previously mentioned, dereferencing this iterator produces a pair object, with the first member the key and the second member the value. In this case second is a Count object, so its val( ) member must be called to produce the actual word count.
Chapter 15: Multiple Inheritance
240

If you want to find the count for a particular word, you can use the array index operator, like this:
cout << "the: " << wordmap["the"].val() << endl;
You can see that one of the great advantages of the map is the clarity of the syntax; an associative array makes intuitive sense to the reader (note, however, that if “the” isn’t already in the wordmap a new entry will be created!).
A command-line argument tool
A problem that often comes up in programming is the management of program arguments that you can specify on the command line. Usually you’d like to have a set of defaults that can be changed via the command line. The following tool expects the command line arguments to be in the form flag1=value1 with no spaces around the ‘=‘ (so it will be treated as a single argument). The ProgVal class simply inherits from map<string, string>:
//: C04:ProgVals.h
// Program values can be changed by command line #ifndef PROGVALS_H
#define PROGVALS_H #include <map> #include <iostream> #include <string>
class ProgVals
: public std::map<std::string, std::string> { public:
ProgVals(std::string defaults[][2], int sz); void parse(int argc, char* argv[],
std::string usage, int offset = 1);
void print(std::ostream& out = std::cout);
};
#endif // PROGVALS_H ///:~
The constructor expects an array of string pairs (as you’ll see, this allows you to initialize it with an array of char*) and the size of that array. The parse( ) member function is handed the command-line arguments along with a “usage” string to print if the command line is given incorrectly, and the “offset” which tells it which command-line argument to start with (so you can have non-flag arguments at the beginning of the command line). Finally, print( ) displays the values. Here is the implementation:
//: C04:ProgVals.cpp {O} #include "ProgVals.h" using namespace std;
ProgVals::ProgVals(
Chapter 15: Multiple Inheritance
241

std::string defaults[][2], int sz) { for(int i = 0; i < sz; i++)
insert(make_pair(
defaults[i][0], defaults[i][1]));
}
void ProgVals::parse(int argc, char* argv[], string usage, int offset) {
//Parse and apply additional
//command-line arguments:
for(int i = offset; i < argc; i++) { string flag(argv[i]);
int equal = flag.find('='); if(equal == string::npos) {
cerr << "Command line error: " << argv[i] << endl << usage << endl;
continue; // Next argument
}
string name = flag.substr(0, equal); string value = flag.substr(equal + 1); if(find(name) == end()) {
cerr << name << endl << usage << endl; continue; // Next argument
}
operator[](name) = value;
}
}
void ProgVals::print(ostream& out) { out << "Program values:" << endl;
for(iterator it = begin(); it != end(); it++) out << (*it).first << " = "
<< (*it).second << endl;
} ///:~
The constructor uses the STL make_pair( ) helper function to convert each pair of char* into a pair object that can be inserted into the map. In parse( ), each command-line argument is checked for the existence of the telltale ‘=‘ sign (reporting an error if it isn’t there), and then is broken into two strings, the name which appears before the ‘=‘, and the value which appears after. The operator[ ] is then used to change the existing value to the new one.
Here’s an example to test the tool:
//: C04:ProgValTest.cpp //{L} ProgVals
Chapter 15: Multiple Inheritance
242

#include "ProgVals.h" using namespace std;
string defaults[][2] = {
{"color", "red" },
{"size", "medium" },
{"shape", "rectangular" },
{"action", "hopping"},
};
const char* usage = "usage:\n"
"ProgValTest [flag1=val1 flag2=val2 ...]\n" "(Note no space around '=')\n"
"Where the flags can be any of: \n" "color, size, shape, action \n";
// So it can be used globally: ProgVals pvals(defaults,
sizeof defaults / sizeof *defaults);
class Animal {
string color, size, shape, action; public:
Animal(string col, string sz, string shp, string act)
:color(col),size(sz),shape(shp),action(act){}
//Default constructor uses program default
//values, possibly change on command line: Animal() : color(pvals["color"]),
size(pvals["size"]), shape(pvals["shape"]), action(pvals["action"]) {}
void print() {
cout << "color = " << color << endl
<<"size = " << size << endl
<<"shape = " << shape << endl
<<"action = " << action << endl;
}
//And of course pvals can be used anywhere
//else you'd like.
};
int main(int argc, char* argv[]) {
// Initialize and parse command line values
Chapter 15: Multiple Inheritance
243