- •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
cout << "removed";
cout << "<br>Thank you</H2>" << endl; } ///:~
Again, all the CGI work is done by the CGImap. From then on it’s a matter of pulling the fields out and looking at them, then deciding what to do about it, which is easy because of the way you can index into a map and also because of the tools available for standard strings. Here, most of the programming has to do with checking for a valid email address. Then a file name is created with the email address as the name and “.add” or “.remove” as the extension, and the email address is placed in the file.
Maintaining your list
Once you have a list of names to add, you can just paste them to end of your list. However, you might get some duplicates so you need a program to remove those. Because your names may differ only by upper and lowercase, it’s useful to create a tool that will read a list of names from a file and place them into a container of strings, forcing all the names to lowercase as it does:
//: C10:readLower.h
//Read a file into a container of string,
//forcing each line to lower case. #ifndef READLOWER_H
#define READLOWER_H #include "../require.h" #include <iostream> #include <fstream> #include <string> #include <algorithm> #include <cctype>
inline char downcase(char c) {
using namespace std; // Compiler bug return tolower(c);
}
std::string lcase(std::string s) { std::transform(s.begin(), s.end(),
s.begin(), downcase); return s;
}
template<class SContainer>
void readLower(char* filename, SContainer& c) { std::ifstream in(filename);
Appendix B: Programming Guidelines
553
assure(in, filename); const int sz = 1024; char buf[sz];
while(in.getline(buf, sz)) // Force to lowercase:
c.push_back(string(lcase(buf)));
}
#endif // READLOWER_H ///:~
Since it’s a template, it will work with any container of string that supports push_back( ). Again, you may want to change the above to the form readln(in, s) instead of using a fixedsized buffer, which is more fragile.
Once the names are read into the list and forced to lowercase, removing duplicates is trivial:
//: C10:RemoveDuplicates.cpp
// Remove duplicate names from a mailing list #include "readLower.h"
#include "../require.h" #include <vector> #include <algorithm> using namespace std;
int main(int argc, char* argv[]) { requireArgs(argc, 2); vector<string> names; readLower(argv[1], names);
long before = names.size();
//You must sort first for unique() to work: sort(names.begin(), names.end());
//Remove adjacent duplicates: unique(names.begin(), names.end()); long removed = before - names.size(); ofstream out(argv[2]);
assure(out, argv[2]); copy(names.begin(), names.end(),
ostream_iterator<string>(out,"\n")); cout << removed << " names removed" << endl;
}///:~
A vector is used here instead of a list because sorting requires random-access which is much faster in a vector. (A list has a built-in sort( ) so that it doesn’t suffer from the performance that would result from applying the normal sort( ) algorithm shown above).
Appendix B: Programming Guidelines
554
The sort must be performed so that all duplicates are adjacent to each other. Then unique( ) can remove all the adjacent duplicates. The program also keeps track of how many duplicate names were removed.
When you have a file of names to remove from your list, readLower( ) comes in handy again:
//: C10:RemoveGroup.cpp
// Remove a group of names from a list #include "readLower.h"
#include "../require.h" #include <list>
using namespace std;
typedef list<string> Container;
int main(int argc, char* argv[]) { requireArgs(argc, 3);
Container names, removals; readLower(argv[1], names); readLower(argv[2], removals); long original = names.size();
Container::iterator rmit = removals.begin(); while(rmit != removals.end())
names.remove(*rmit++); // Removes all matches ofstream out(argv[3]);
assure(out, argv[3]); copy(names.begin(), names.end(),
ostream_iterator<string>(out,"\n")); long removed = original - names.size();
cout << "On removal list: " << removals.size()
<<"\n Removed: " << removed << endl;
}///:~
Here, a list is used instead of a vector (since readLower( ) is a template, it adapts). Although there is a remove( ) algorithm that can be applied to containers, the built-in list::remove( ) seems to work better. The second command-line argument is the file containing the list of names to be removed. An iterator is used to step through that list, and the list::remove( ) function removes every instance of each name from the master list. Here, the list doesn’t need to be sorted first.
Unfortunately, that’s not all there is to it. The messiest part about maintaining a mailing list is the bounced messages. Presumably, you’ll just want to remove the addresses that produce bounces. If you can combine all the bounced messages into a single file, the following program has a pretty good chance of extracting the email addresses; then you can use RemoveGroup to delete them from your list.
Appendix B: Programming Guidelines
555
//: C10:ExtractUndeliverable.cpp
//Find undeliverable names to remove from
//mailing list from within a mail file
//containing many messages
#include "../require.h" #include <cstdio> #include <string> #include <set>
using namespace std;
char* start_str[] = { "following address", "following recipient", "following destination",
"undeliverable to the following", "following invalid",
};
char* continue_str[] = { "Message-ID",
"Please reply to",
};
//The in() function allows you to check whether
//a string in this set is part of your argument. class StringSet {
char** ss; int sz;
public:
StringSet(char** sa, int sza):ss(sa),sz(sza) {} bool in(char* s) {
for(int i = 0; i < sz; i++) if (strstr(s, ss[i]) != 0)
return true; return false;
}
};
//Calculate array length:
#define ALEN(A) ((sizeof A)/(sizeof *A))
StringSet
starts(start_str, ALEN(start_str)),
Appendix B: Programming Guidelines
556
continues(continue_str, ALEN(continue_str));
int main(int argc, char* argv[]) { requireArgs(argc, 2,
"Usage:ExtractUndeliverable infile outfile"); FILE* infile = fopen(argv[1], "rb");
FILE* outfile = fopen(argv[2], "w"); require(infile != 0); require(outfile != 0); set<string> names;
const int sz = 1024; char buf[sz];
while(fgets(buf, sz, infile) != 0) { if(starts.in(buf)) {
puts(buf);
while(fgets(buf, sz, infile) != 0) { if(continues.in(buf)) continue; if(strstr(buf, "---") != 0) break;
const char* delimiters= " \t<>():;,\n\""; char* name = strtok(buf, delimiters); while(name != 0) {
if(strstr(name, "@") != 0) names.insert(string(name));
name = strtok(0, delimiters);
}
}
}
}
set<string>::iterator i = names.begin(); while(i != names.end())
fprintf(outfile, "%s\n", (*i++).c_str()); } ///:~
The first thing you’ll notice about this program is that contains some C functions, including C I/O. This is not because of any particular design insight. It just seemed to work when I used the C elements, and it started behaving strangely with C++ I/O. So the C is just because it works, and you may be able to rewrite the program in more “pure C++” using your C++ compiler and produce correct results.
A lot of what this program does is read lines looking for string matches. To make this convenient, I created a StringSet class with a member function in( ) that tells you whether any of the strings in the set are in the argument. The StringSet is initialized with a constant two-dimensional of strings and the size of that array. Although the StringSet makes the code easier to read, it’s also easy to add new strings to the arrays.
Appendix B: Programming Guidelines
557