- •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
(*it)->accept(sval);
cout << string(sval) << endl;
}
// Perform "Bee" operation on all Flowers: Bee bee;
for(it = v.begin(); it != v.end(); it++) (*it)->accept(bee);
purge(v); } ///:~
Efficiency
Flyweight
The composite
Evolving a design: the trash recycler
The nature of this problem (modeling a trash recycling system) 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. In the initial solution, RTTI (described in Chapter XX) is used.
This is not a trivial design because it has an added constraint. That’s what makes it interesting
– it’s more like the messy problems you’re likely to encounter in your work. The extra constraint is that the trash arrives at the trash recycling plant all mixed together. The program must model the sorting of that trash. This is where RTTI comes in: you have a bunch of anonymous pieces of trash, and the program figures out exactly what type they are.
One of the objectives of this program is to sum up the weight and value of the different types of trash. The trash will be kept in (potentially different types of) containers, so it makes sense to templatize the “summation” function on the container holding it (assuming that container exhibits basic STL-like behavior), so the function will be maximally flexible:
//: C09:sumValue.h
//Sums the value of Trash in any type of STL
//container of any specific type of Trash:
Chapter 16: Design Patterns |
466 |
#ifndef SUMVALUE_H #define SUMVALUE_H #include <typeinfo> #include <vector>
template<typename Cont>
void sumValue(const Cont& bin) { double val = 0.0f;
typename Cont::iterator tally = bin.begin(); while(tally != bin.end()) {
val +=(*tally)->weight() * (*tally)->value(); out << "weight of "
<<typeid(*(*tally)).name()
<<" = " << (*tally)->weight()
<<endl;
tally++;
}
out << "Total value = " << val << endl;
}
#endif // SUMVALUE_H ///:~
When you look at a piece of code like this, it can be initially disturbing because you might wonder “how can the compiler know that the member functions I’m calling here are valid?” But of course, all the template says is “generate this code on demand,” and so only when you call the function will type checking come into play. This enforces that *tally produces an object that has member functions weight( ) and value( ), and that out is a global ostream.
The sumValue( ) function is templatized on the type of container that’s holding the Trash pointers. Notice there’s nothing in the template signature that says “this container must behave like an STL container and must hold Trash*”; that is all implied in the code that’s generated which uses the container.
The first version of the example takes the straightforward approach: creating a vector<Trash*>, filling it with Trash objects, then using RTTI to sort them out:
//: C09:Recycle1.cpp // Recycling with RTTI #include "sumValue.h" #include "../purge.h" #include <fstream> #include <vector> #include <typeinfo> #include <cstdlib> #include <ctime>
using namespace std;
ofstream out("Recycle1.out");
Chapter 16: Design Patterns |
467 |
class Trash { double _weight;
static int _count; // # created static int _dcount; // # destroyed
//disallow automatic creation of
//assignment & copy-constructor: void operator=(const Trash&); Trash(const Trash&);
public:
Trash(double wt) : _weight(wt) { _count++;
}
virtual double value() const = 0;
double weight() const { return _weight; } static int count() { return _count; } static int dcount() { return _dcount;} virtual ~Trash() { _dcount++; }
};
int Trash::_count = 0; int Trash::_dcount = 0;
class Aluminum : public Trash { static double val;
public:
Aluminum(double wt) : Trash(wt) {} double value() const { return val; } static void value(double newval) {
val = newval;
}
~Aluminum() { out << "~Aluminum\n"; }
};
double Aluminum::val = 1.67F;
class Paper : public Trash { static double val;
public:
Paper(double wt) : Trash(wt) {} double value() const { return val; } static void value(double newval) {
val = newval;
}
~Paper() { out << "~Paper\n"; }
Chapter 16: Design Patterns |
468 |
};
double Paper::val = 0.10F;
class Glass : public Trash { static double val;
public:
Glass(double wt) : Trash(wt) {} double value() const { return val; } static void value(double newval) {
val = newval;
}
~Glass() { out << "~Glass\n"; }
};
double Glass::val = 0.23F;
class TrashGen { public:
TrashGen() { srand(time(0)); } static double frand(int mod) {
return static_cast<double>(rand() % mod);
}
Trash* operator()() {
for(int i = 0; i < 30; i++) switch(rand() % 3) {
case 0 :
return new Aluminum(frand(100)); case 1 :
return new Paper(frand(100)); case 2 :
return new Glass(frand(100));
}
return new Aluminum(0); // Or throw exeception...
}
};
int main() { vector<Trash*> bin;
// Fill up the Trash bin: generate_n(back_inserter(bin), 30, TrashGen()); vector<Aluminum*> alBin;
vector<Paper*> paperBin;
Chapter 16: Design Patterns |
469 |
vector<Glass*> glassBin; vector<Trash*>::iterator sorter = bin.begin(); // Sort the Trash:
while(sorter != bin.end()) { Aluminum* ap =
dynamic_cast<Aluminum*>(*sorter);
Paper* pp = dynamic_cast<Paper*>(*sorter); Glass* gp = dynamic_cast<Glass*>(*sorter); if(ap) alBin.push_back(ap);
if(pp) paperBin.push_back(pp); if(gp) glassBin.push_back(gp); sorter++;
}
sumValue(alBin);
sumValue(paperBin);
sumValue(glassBin);
sumValue(bin);
out << "total created = "
<<Trash::count() << endl; purge(bin);
out << "total destroyed = "
<<Trash::dcount() << endl;
}///:~
This uses the classic structure of virtual functions in the base class that are redefined in the derived class. In addition, there are two static data members in the base class: _count to indicate the number of Trash objects that are created, and _dcount to keep track of the number that are destroyed. This verifies that proper memory management occurs. To support this, the operator= and copy-constructor are disallowed by declaring them private (no definitions are necessary; this simply prevents the compiler from synthesizing them). Those operations would cause problems with the count, and if they were allowed you’d have to define them properly.
The Trash objects are created, for the sake of this example, by the generator TrashGen, which uses the random number generator to choose the type of Trash, and also to provide it with a “weight” argument. The return value of the generator’s operator( ) is upcast to Trash*, so all the specific type information is lost. In main( ), a vector<Trash*> called bin is created and then filled using the STL algorithm generate_n( ). To perform the sorting, three vectors are created, each of which holds a different type of Trash*. An iterator moves through bin and RTTI is used to determine which specific type of Trash the iterator is currently selecting, placing each into the appropriate typed bin. Finally, sumValue( ) is applied to each of the containers, and the Trash objects are cleaned up using purge( ) (defined in Chapter XX). The creation and destruction counts ensure that things are properly cleaned up.
Of course, it seems silly to upcast the types of Trash into a container holding base type pointers, and then to turn around and downcast. Why not just put the trash into the appropriate
Chapter 16: Design Patterns |
470 |