- •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 example begins with the Poingable and Bingable interfaces, each of which contain a single member function. The services provided by callPoing( ) and callBing( ) require that the object they receive implement the Poingable and Bingable interfaces, respectively, but they put no other requirements on that object so as to maximize the flexibility of using callPoing( ) and callBing( ). Note the lack of virtual destructors in either interface – the intent is that you never perform object destruction via the interface.
Outer contains some private data (name) and it wishes to provide both a Poingable interface and a Bingable interface so it can be used with callPoing( ) and callBing( ). Of course, in this situation we could simply use multiple inheritance. This example is just intended to show the simplest syntax for the idiom; we’ll see a real use shortly. To provide a Poingable object without inheriting Outer from Poingable, the inner class idiom is used. First, the declaration class Inner says that, somewhere, there is a nested class of this name. This allows the friend declaration for the class, which follows. Finally, now that the nested class has been granted access to all the private elements of Outer, the class can be defined. Notice that it keeps a pointer to the Outer which created it, and this pointer must be initialized in the constructor. Finally, the poing( ) function from Poingable is implemented. The same process occurs for the second inner class which implements Bingable. Each inner class has a single private instance created, which is initialized in the Outer constructor. By creating the member objects and returning references to them, issues of object lifetime are eliminated.
Notice that both inner class definitions are private, and in fact the client programmer doesn’t have any access to details of the implementation, since the two access methods operator Poingable&( ) and operator Bingable&( ) only return a reference to the upcast interface, not to the object that implements it. In fact, since the two inner classes are private, the client programmer cannot even downcast to the implementation classes, thus providing complete isolation between interface and implementation.
Just to push a point, I’ve taken the extra liberty here of defining the automatic type conversion operators operator Poingable&( ) and operator Bingable&( ). In main( ), you can see that these actually allow a syntax that looks like Outer is multiply inherited from Poingable and Bingable. The difference is that the casts in this case are one way. You can get the effect of an upcast to Poingable or Bingable, but you cannot downcast back to an Outer. In the following example of observer, you’ll see the more typical approach: you provide access to the inner class objects using ordinary member functions, not automatic type conversion operations.
The observer example
Armed with the Observer and Observable header files and the inner class idiom, we can look at an example of the observer pattern:
//: C09:ObservedFlower.cpp
// Demonstration of "observer" pattern #include "Observable.h"
#include <iostream> #include <vector>
Chapter 16: Design Patterns |
455 |
#include <algorithm> #include <string> using namespace std;
class Flower { bool isOpen;
public:
Flower() : isOpen(false), openNotifier(this), closeNotifier(this) {}
void open() { // Opens its petals isOpen = true; openNotifier.notifyObservers(); closeNotifier.open();
}
void close() { // Closes its petals isOpen = false; closeNotifier.notifyObservers(); openNotifier.close();
}
// Using the "inner class" idiom: class OpenNotifier;
friend class Flower::OpenNotifier;
class OpenNotifier : public Observable { Flower* parent;
bool alreadyOpen; public:
OpenNotifier(Flower* f) : parent(f), alreadyOpen(false) {}
void notifyObservers(Argument* arg=0) { if(parent->isOpen && !alreadyOpen) {
setChanged();
Observable::notifyObservers(); alreadyOpen = true;
}
}
void close() { alreadyOpen = false; } } openNotifier;
class CloseNotifier;
friend class Flower::CloseNotifier;
class CloseNotifier : public Observable { Flower* parent;
bool alreadyClosed; public:
CloseNotifier(Flower* f) : parent(f),
Chapter 16: Design Patterns |
456 |
alreadyClosed(false) {}
void notifyObservers(Argument* arg=0) { if(!parent->isOpen && !alreadyClosed) {
setChanged();
Observable::notifyObservers(); alreadyClosed = true;
}
}
void open() { alreadyClosed = false; } } closeNotifier;
};
class Bee { string name;
// An "inner class" for observing openings: class OpenObserver;
friend class Bee::OpenObserver;
class OpenObserver : public Observer { Bee* parent;
public:
OpenObserver(Bee* b) : parent(b) {} void update(Observable*, Argument *) {
cout << "Bee " << parent->name << "'s breakfast time!\n";
}
} openObsrv;
// Another "inner class" for closings: class CloseObserver;
friend class Bee::CloseObserver;
class CloseObserver : public Observer { Bee* parent;
public:
CloseObserver(Bee* b) : parent(b) {} void update(Observable*, Argument *) {
cout << "Bee " << parent->name << "'s bed time!\n";
}
} closeObsrv; public:
Bee(string nm) : name(nm), openObsrv(this), closeObsrv(this) {}
Observer& openObserver() { return openObsrv; } Observer& closeObserver() { return closeObsrv;}
};
Chapter 16: Design Patterns |
457 |
class Hummingbird { string name;
class OpenObserver;
friend class Hummingbird::OpenObserver; class OpenObserver : public Observer {
Hummingbird* parent; public:
OpenObserver(Hummingbird* h) : parent(h) {} void update(Observable*, Argument *) {
cout << "Hummingbird " << parent->name << "'s breakfast time!\n";
}
} openObsrv;
class CloseObserver;
friend class Hummingbird::CloseObserver; class CloseObserver : public Observer {
Hummingbird* parent; public:
CloseObserver(Hummingbird* h) : parent(h) {} void update(Observable*, Argument *) {
cout << "Hummingbird " << parent->name << "'s bed time!\n";
}
} closeObsrv; public:
Hummingbird(string nm) : name(nm), openObsrv(this), closeObsrv(this) {}
Observer& openObserver() { return openObsrv; } Observer& closeObserver() { return closeObsrv;}
};
int main() { Flower f;
Bee ba("A"), bb("B"); Hummingbird ha("A"), hb("B");
f.openNotifier.addObserver(ha.openObserver());
f.openNotifier.addObserver(hb.openObserver());
f.openNotifier.addObserver(ba.openObserver());
f.openNotifier.addObserver(bb.openObserver());
f.closeNotifier.addObserver(ha.closeObserver());
f.closeNotifier.addObserver(hb.closeObserver());
f.closeNotifier.addObserver(ba.closeObserver());
f.closeNotifier.addObserver(bb.closeObserver());
Chapter 16: Design Patterns |
458 |