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

sumValue(glassBin, out); sumValue(bin, out); purge(bin);
} ///:~
The nature of this problem is that the trash is thrown unclassified into a single bin, so the specific type information is lost. But later, the specific type information must be recovered to properly sort the trash, and so RTTI is used. In Chapter XX, an RTTI system was inserted into the class hierarchy, but as you can see here, it’s more convenient to use C++’s built-in RTTI.
Mechanism & overhead of RTTI
Typically, RTTI is implemented by placing an additional pointer in the VTABLE. This pointer points to the typeinfo structure for that particular type. (Only one instance of the typeinfo structure is created for each new class.) So the effect of a typeid( ) expression is quite simple: The VPTR is used to fetch the typeinfo pointer, and a reference to the resulting typeinfo structure is produced. Also, this is a deterministic process – you always know how long it’s going to take.
For a dynamic_cast<destination*>(source_pointer), most cases are quite straightforward: source_pointer’s RTTI information is retrieved, and RTTI information for the type destination* is fetched. Then a library routine determines whether source_pointer’s type is of type destination* or a base class of destination*. The pointer it returns may be slightly adjusted because of multiple inheritance if the base type isn’t the first base of the derived class. The situation is (of course) more complicated with multiple inheritance where a base type may appear more than once in an inheritance hierarchy and where virtual base classes are used.
Because the library routine used for dynamic_cast must check through a list of base classes, the overhead for dynamic_cast is higher than typeid( ) (but of course you get different information, which may be essential to your solution), and it’s nondeterministic because it may take more time to discover a base class than a derived class. In addition, dynamic_cast allows you to compare any type to any other type; you aren’t restricted to comparing types within the same hierarchy. This adds extra overhead to the library routine used by dynamic_cast.
Creating your own RTTI
If your compiler doesn’t yet support RTTI, you can build it into your class libraries quite easily. This makes sense because RTTI was added to the language after observing that virtually all class libraries had some form of it anyway (and it was relatively “free” after
Chapter 17: Run-Time Type Identification
416

exception handling was added because exceptions require exact knowledge of type information).
Essentially, RTTI requires only a virtual function to identify the exact type of the class, and a function to take a pointer to the base type and cast it down to the more derived type; this function must produce a pointer to the more derived type. (You may also wish to handle references.) There are a number of approaches to implement your own RTTI, but all require a unique identifier for each class and a virtual function to produce type information. The following uses a static member function called dynacast( ) that calls a type information function dynamic_type( ). Both functions must be defined for each new derivation:
//: C08:Selfrtti.cpp
// Your own RTTI system #include "../purge.h" #include <iostream> #include <vector>
using namespace std;
class Security { protected:
static const int baseID = 1000; public:
virtual int dynamic_type(int id) { if(id == baseID) return 1; return 0;
}
};
class Stock : public Security { protected:
static const int typeID = baseID + 1; public:
int dynamic_type(int id) { if(id == typeID) return 1;
return Security::dynamic_type(id);
}
static Stock* dynacast(Security* s) { if(s->dynamic_type(typeID))
return (Stock*)s; return 0;
}
};
class Bond : public Security {
Chapter 17: Run-Time Type Identification
417

protected:
static const int typeID = baseID + 2 ; public:
int dynamic_type(int id) { if(id == typeID) return 1;
return Security::dynamic_type(id);
}
static Bond* dynacast(Security* s) { if(s->dynamic_type(typeID))
return (Bond*)s; return 0;
}
};
class Commodity : public Security { protected:
static const int typeID = baseID + 3; public:
int dynamic_type(int id) { if(id == typeID) return 1;
return Security::dynamic_type(id);
}
static Commodity* dynacast(Security* s) { if(s->dynamic_type(typeID))
return (Commodity*)s; return 0;
}
void special() {
cout << "special Commodity function\n";
}
};
class Metal : public Commodity { protected:
static const int typeID = baseID + 4; public:
int dynamic_type(int id) { if(id == typeID) return 1;
return Commodity::dynamic_type(id);
}
static Metal* dynacast(Security* s) { if(s->dynamic_type(typeID))
return (Metal*)s;
Chapter 17: Run-Time Type Identification
418

return 0;
}
};
int main() {
vector<Security*> portfolio; portfolio.push_back(new Metal); portfolio.push_back(new Commodity); portfolio.push_back(new Bond); portfolio.push_back(new Stock); vector<Security*>::iterator it =
portfolio.begin();
while(it != portfolio.end()) {
Commodity* cm = Commodity::dynacast(*it); if(cm) cm->special();
else cout << "not a Commodity" << endl; it++;
}
cout << "cast from intermediate pointer:\n"; Security* sp = new Metal;
Commodity* cp = Commodity::dynacast(sp); if(cp) cout << "it's a Commodity\n"; Metal* mp = Metal::dynacast(sp);
if(mp) cout << "it's a Metal too!\n"; purge(portfolio);
} ///:~
Each subclass must create its own typeID, redefine the virtual dynamic_type( ) function to return that typeID, and define a static member called dynacast( ), which takes the base pointer (or a pointer at any level in a deeper hierarchy – in that case, the pointer is simply upcast).
In the classes derived from Security, you can see that each defines its own typeID enumeration by adding to baseID. It’s essential that baseID be directly accessible in the derived class because the enum must be evaluated at compile-time, so the usual approach of reading private data with an inline function would fail. This is a good example of the need for the protected mechanism.
The enum baseID establishes a base identifier for all types derived from Security. That way, if an identifier clash ever occurs, you can change all the identifiers by changing the base value. (However, because this scheme doesn’t compare different inheritance trees, an identifier clash is unlikely). In all the classes, the class identifier number is protected, so it’s directly available to derived classes but not to the end user.
This example illustrates what built-in RTTI must cope with. Not only must you be able to determine the exact type, you must also be able to find out whether your exact type is derived
Chapter 17: Run-Time Type Identification
419