- •Introduction
- •Saving Time with This Book
- •Conventions Used in This Book
- •Part II: Working with the Pre-Processor
- •Part III: Types
- •Part IV: Classes
- •Part V: Arrays and Templates
- •Part VI: Input and Output
- •Part VII: Using the Built-in Functionality
- •Part VIII: Utilities
- •Part IX: Debugging C++ Applications
- •Part X: The Scary (or Fun!) Stuff
- •Icons Used in This Book
- •Creating and Implementing an Encapsulated Class
- •Creating a Mailing-List Application
- •Testing the Mailing-List Application
- •Customizing a Class with Polymorphism
- •Testing the Virtual Function Code
- •Why Do the Destructors Work?
- •Delayed Construction
- •The cDate Class
- •Testing the cDate Class
- •Creating the Header File
- •Testing the Header File
- •The Assert Problem
- •Fixing the Assert Problem
- •Using the const Construct
- •Identifying the Errors
- •Fixing the Errors
- •Fixing What Went Wrong with the Macro
- •Using Macros Appropriately
- •Using the sizeof Function
- •Evaluating the Results
- •Using sizeof with Pointers
- •Implementing the Range Class
- •Testing the Range Class
- •Creating the Matrix Class
- •Matrix Operations
- •Multiplying a Matrix by a Scalar Value
- •Multiplying a Matrix by Scalar Values, Take 2
- •Testing the Matrix Class
- •Implementing the Enumeration Class
- •Testing the Enumeration Class
- •Implementing Structures
- •Interpreting the Output
- •Defining Constants
- •Testing the Constant Application
- •Using the const Keyword
- •Illustrating Scope
- •Interpreting the Output
- •Using Casts
- •Addressing the Compiler Problems
- •Testing the Changes
- •Implementing Member-Function Pointers
- •Updating Your Code with Member-Function Pointers
- •Testing the Member Pointer Code
- •Customizing Functions We Wrote Ourselves
- •Testing the Default Code
- •Fixing the Problem
- •Testing the Complete Class
- •Implementing Virtual Inheritance
- •Correcting the Code
- •Rules for Creating Overloaded Operators
- •Using Conversion Operators
- •Using Overloaded Operators
- •Testing the MyString Class
- •Rules for Implementing new and delete Handlers
- •Overloading new and delete Handlers
- •Testing the Memory Allocation Tracker
- •Implementing Properties
- •Testing the Property Class
- •Implementing Data Validation with Classes
- •Testing Your SSN Validator Class
- •Creating the Date Class
- •Testing the Date Class
- •Some Final Thoughts on the Date Class
- •Creating a Factory Class
- •Testing the Factory
- •Enhancing the Manager Class
- •Implementing Mix-In Classes
- •Testing the Template Classes
- •Implementing Function Templates
- •Creating Method Templates
- •Using the Vector Class
- •Creating the String Array Class
- •Working with Vector Algorithms
- •Creating an Array of Heterogeneous Objects
- •Creating the Column Class
- •Creating the Row Class
- •Creating the Spreadsheet Class
- •Testing Your Spreadsheet
- •Working with Streams
- •Testing the File-Reading Code
- •Creating the Test File
- •Reading Delimited Files
- •Testing the Code
- •Creating the XML Writer
- •Testing the XML Writer
- •Creating the Configuration-File Class
- •Setting Up Your Test File
- •Building the Language Files
- •Creating an Input Text File
- •Reading the International File
- •Testing the String Reader
- •Creating a Translator Class
- •Testing the Translator Class
- •Creating a Virtual File Class
- •Testing the Virtual File Class
- •Using the auto_ptr Class
- •Creating a Memory Safe Buffer Class
- •Throwing and Logging Exceptions
- •Dealing with Unhandled Exceptions
- •Re-throwing Exceptions
- •Creating the Wildcard Matching Class
- •Testing the Wildcard Matching Class
- •Creating the URL Codec Class
- •Testing the URL Codec Class
- •Testing the Rot13 Algorithm
- •Testing the XOR Algorithm
- •Implementing the transform Function to Convert Strings
- •Testing the String Conversions
- •Implementing the Serialization Interface
- •Creating the Buffer Class
- •Testing the Buffer Class
- •Creating the Multiple-Search-Path Class
- •Testing the Multiple-Search-Path Class
- •Testing the Flow Trace System
- •The assert Macro
- •Logging
- •Testing the Logger Class
- •Design by Contract
- •Adding Logging to the Application
- •Making Functions Inline
- •Avoiding Temporary Objects
- •Passing Objects by Reference
- •Choosing Initialization Instead of Assignment
- •Learning How Code Operates
- •Testing the Properties Class
- •Creating the Locking Mechanism
- •Testing the Locking Mechanism
- •Testing the File-Guardian Class
- •Implementing the Complex Class
- •Creating the Conversion Code
- •Testing the Conversion Code
- •A Sample Program
- •Componentizing
- •Restructuring
- •Specialization
- •Index
Testing the Translator Class 281
This code simply manages the translation operations. The code consists of three basic functions:
The class manages an input file that contains the mappings of old text to the new text we
wish to replace it with. (See 1.)
This method takes a filename, attempts
to open it, and reads in the data pairs if the open operation was successful.
The class manages the storage of the mappings
in a hash table. (See 2.)
This is done by the hash table, represented by the Standard Template Library map class.
The class replaces a given string with the string
desired in the final output. (Shown at 3.) Note that we simply pass in each string and replace it if it is found in the hash table. If it is not, we return the original string. This allows the application to save time by not bothering to check if the string needs to be replaced
or not.
3.Save the source code as a file in your code editor.
This dictionary will do all the work of loading data from a translation file and replacing individual words with specified translated words. For example, consider the idea of replacing all occurrences of the word good with the word bad. If we were given the input string good day, I am having a good time at this goodly party, we would translate this into bad day, I am having a bad time at this goodly party. Note that we only replace full matches, not text that appears anywhere in the input string. The class now exists, the only thing we need to do is use it.
Testing the Translator Class
After you create the dictionary, you should create a test driver that not only ensures that your code is correct, but also shows people how to use your code.
The following steps show you how to create a test driver that illustrates various kinds of input from the user, and shows how the class is intended to be used:
1. In the code editor of your choice, reopen the source file to hold the code for your test program.
In this example, I named the test program ch47.cpp.
2. Append the code from Listing 47-2 into your file.
Better yet, copy the code from the source file on this book’s companion Web site.
LISTING 47-2: THE TRANSLATOR CLASS TEST DRIVER
int main() |
|
|
{ |
|
4 |
Translator t(“translate.txt”); |
|
|
printf(“Enter some text to translate: |
|
|
“); |
|
|
string in; |
|
|
bool bDone = false; |
|
5 |
while ( !bDone ) |
|
|
{ |
|
|
char c; |
|
|
cin.get(c); |
|
|
if ( c == ‘\n’ ) |
|
|
bDone = true; |
|
|
else |
|
|
in += c; |
|
|
} |
|
|
printf(“Initial string: %s\n”, |
|
|
in.c_str() ); |
|
|
// Break it down into words. |
|
|
string word; |
|
|
for ( int i=0; i<in.length(); ++i ) |
|
|
{ |
|
|
if ( in[i] == ‘ ‘ ) |
|
|
{ |
|
6 |
if ( word.length() ) |
|
|
{ |
|
string sOut = t.replace( word );
cout << sOut << “ “;
}
(continued)
282 Technique 47: Hashing Out Translations
LISTING 47-2 (continued)
word = “”; |
|
|
} |
|
|
else |
|
|
word += in[i]; |
|
|
} |
|
7 |
if ( word.length() ) |
|
|
{ |
|
string sOut = t.replace( word ); cout << sOut << “ “;
}
}
The code above simply reads input from the keyboard, breaks it down into words, and then calls the translator to replace any portions of the string that need to be translated from our input file. The translator operates on a file called translate.txt, as shown at 4. You can easily change this filename, or even pass one in on the command line, if you wish. Each line is read from the keyboard, one character at a time, until a car-
riage return is encountered. (See |
5.) Finally, we |
|
parse the input line until we encounter |
a space |
(shown at 6) or the end of the string (shown at 7). When this happens, we replace the “word” we have parsed by calling the replace method of the translator.
3. Save the source-code file in the editor and then close the editor application.
4. Compile the source file, using your favorite compiler on your favorite operating system.
5. In your code editor, create a translation-text file.
This file will contain the pairs of words to be used in the translation file; each pair consists of a word and the translated version of that word. I called mine translation.txt, but you can call it whatever you like.
6. Put the following text into the translation.txt file:
good bad insult dis talk jive
You can place any words you want in the file. The first word will be replaced by the second word in any sentence you input into the system.
7. Run the program on your favorite operating system.
If you have done everything properly, you should see output resembling this:
$ ./a.exe
Enter some text to translate: Initial string: you are so good to
talk and not insult me
you are so bad to jive and not dis me
48 Implementing
Virtual Files
Technique
Save Time By
Understanding virtual files
Creating a virtual file class
Testing your class
These days, application data can be very large and can consume a lot of your memory. Depending on your application footprint and target operating system, loading the entire application file into
memory at one time may be impossible. Memory shortages are common with embedded systems, and with hand-held devices, where only a limited amount of memory is available to share between numerous applications. There are a number of ways to handle such conditions, from reading in only as much data as you can and not storing the remainder, to limiting the data to chunks and forcing the user to select which chunk they want. None of these solutions, however, is quite as elegant to either the developer or the end-user as the virtual file. A virtual file is a window into the file you are trying to process. It appears to end-users as if they’re seeing the whole file at once — but they’re really seeing just one small piece of it at a time. If you build virtual views of your files into your application up front, you save time in the long run, because you won’t have to go back and redesign your applications when the files become larger than you were expecting.
The capability to provide a virtual window into a file not only conserves memory, it also conserves speed. By loading only a small chunk of the file at any given moment, you can load immense files in no time and all, and page through them very quickly. This method is used by many large text editors.
Creating a Virtual File Class
In order to manage virtual files, we will need two different classes. First, we will need a single class that manages a given chunk of data from the file. This class will manage the data in that chunk, as well as keep track of where that particular piece of data was read from the file and how big it is. After this, we need to have a manager that keeps track of all of those chunks of data, allocating new objects to manage the individual pieces that are read in, and deleting the pieces that are no longer used. Let’s create a few classes to do that now.
284 |
Technique 48: Implementing Virtual Files |
|
1. In the code editor of your choice, create a new |
2. Type the code given in Listing 48-1 into your |
|
file to hold the code for the source file of the |
file. |
technique.
Better yet, copy the code from the source file on
In this example, the file is named ch48.cpp, although you can use whatever you choose. This file will contain the class definition for our virtual file manager objects.
this book’s companion Web site.
LISTING 48-1: THE VIRTUAL FILE MANAGER CLASSES
#include <iostream> #include <string> #include <vector> #include <fstream>
using namespace std;
class FileChunk |
|
1 |
|
{ |
|
|
|
private: |
|
|
|
string |
_chunk; |
|
|
long |
_offset; |
|
|
long |
_length; |
|
|
bool |
_inuse; |
|
|
long |
_accesses; |
|
|
protected: |
|
|
|
void Clear()
{
_offset = -1; _length = -1; _chunk = “”; _inuse = false; _accesses = 0;
}
void Copy( const FileChunk& aCopy )
{
_offset = aCopy._offset; _length = aCopy._length; _chunk = aCopy._chunk; _inuse = aCopy._inuse;
}
bool Read ( ifstream& in, long pos, long length )
{
_offset = pos; _chunk = “”; _length = 0;
// Seek to the position in the stream. in.seekg( pos );
if ( in.fail() ) return false;
Creating a Virtual File Class 285
//Read up to the end of the file or the last of the length
//bytes.
for ( int i=0; i<length; ++i )
{
char c; in.get(c);
if ( in.fail() ) break;
_length ++; _chunk += c;
}
_inuse = true;
}
public:
FileChunk( void )
{
Clear();
}
FileChunk( ifstream& in, long pos, long length )
{
Clear();
Read( in, pos, length );
}
FileChunk( const FileChunk& aCopy )
{
Clear(); Copy( aCopy );
}
FileChunk operator=( const FileChunk& aCopy )
{
Clear(); Copy( aCopy ); return *this;
}
// Accessors long Offset()
{
return _offset;
}
long Length()
{
return _length;
}
string& Chunk()
{
_accesses ++; return _chunk;
}
bool InUse(void)
{
return _inuse;
(continued)
286 Technique 48: Implementing Virtual Files
LISTING 48-1 (continued)
}
void setOffset( long offset )
{
_offset = _offset;
}
void setLength( long length )
{
_length = length;
}
void setChunk( const string& chunk )
{
_chunk = chunk;
}
long AccessCount( void )
{
return _accesses;
}
bool Load( ifstream& in, long offset, long length )
{
Clear();
return Read( in, offset, length );
}
}; |
|
|
|
const int kChunkSize = 128; |
|
|
|
class FileChunkManager |
|
2 |
|
{ |
|
|
|
private: |
|
|
|
int |
_numChunks; |
|
|
FileChunk |
*_chunks; |
|
|
ifstream |
_in; |
|
|
string |
_fileName; |
|
|
protected: |
|
|
|
FileChunk *findChunk( long theOffset )
{
for ( int i=0; i<_numChunks; ++i )
{
if ( _chunks[i].InUse() == true )
{
long offset = _chunks[i].Offset(); long length = _chunks[i].Length();
if ( theOffset >= offset && theOffset <= offset+length ) return &_chunks[i];
}
}
return NULL;
}
FileChunk *addChunk( long theOffset )
Creating a Virtual File Class 287
{
for ( int i=0; i<_numChunks; ++i )
{
if ( _chunks[i].InUse() == false )
{
if ( _chunks[i].Load( _in, theOffset, kChunkSize ) ) return &_chunks[i];
}
}
return NULL;
}
FileChunk *getLeastRecentlyAccessed()
{
int idx = 0;
long access = _chunks[0].AccessCount();
for ( int i=0; i<_numChunks; ++i )
{
if ( _chunks[i].InUse() == true )
{
if ( _chunks[i].AccessCount() < access )
{
idx = i;
access = _chunks[i].AccessCount();
}
}
}
return &_chunks[idx];
}
public:
FileChunkManager(void)
{
_numChunks = 0; _chunks = NULL;
}
FileChunkManager( const char *fileName, int nMaxChunks )
{
_numChunks = nMaxChunks;
_chunks = new FileChunk[ nMaxChunks ]; _fileName = fileName;
_in.open( fileName );
}
FileChunkManager( const FileChunkManager& aCopy )
{
_numChunks = aCopy._numChunks;
_chunks = new FileChunk[ _numChunks ]; for ( int i=0; i<_numChunks; ++i )
_chunks[i] = aCopy._chunks[i]; _fileName = aCopy._fileName; _in.open( _fileName.c_str() );
}
(continued)
288 Technique 48: Implementing Virtual Files
LISTING 48-1 (continued)
virtual ~FileChunkManager( void )
{
delete [] _chunks;
}
char operator[]( long offset )
{
// Find which chunk this offset is in. FileChunk *chunk = findChunk( offset ); if ( chunk == NULL )
{
// There are none. See whether we can add one. chunk = addChunk( offset );
}
//If we have one, just get the data from it.
//Otherwise, we have to go dump one.
if ( !chunk )
{
chunk = getLeastRecentlyAccessed(); chunk->Load( _in, offset, kChunkSize );
}
// Finally, extract the piece we need. int pos = offset - chunk->Offset(); return chunk->Chunk()[pos];
}
// Dump the function to illustrate what is in the chunks. void Dump(void)
{
for ( int i=0; i<_numChunks; ++i )
{
printf(“Chunk %d: %s\n”, i, _chunks[i].InUse() ? “In Use” : “NOT Used” ); printf(“Offset: %ld\n”, _chunks[i].Offset() );
printf(“Length: %ld\n”, _chunks[i].Length() ); if ( _chunks[i].InUse() )
printf(“String: [%s]\n”, _chunks[i].Chunk().c_str() );
}
}
};
The code above shows the two basic classes: the |
the FileChunkManager class, shown at 2, main- |
|
FileChunk and FileChunkManager classes. The |
tains an array of FileChunk objects, keeping |
track |
FileChunk class, shown at 1, manages a single |
of which ones are in use and what their offsets are. |
|
chunk of data in the file. This data includes the |
When a block of the file is requested, the man- |
|
offset within the file, the file object itself, and the |
ager object looks through its list to see if there is |
|
text from that location in the file. It also stores |
a block that has that data in it and if so, requests |
|
the length of the chunk that was actually read in, |
that particular text from that particular object. |
|
since some chunks at the end of the file could be |
3. Save the source file in the code editor. |
|
smaller than the full size block. The second class, |
|