- •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
350 Technique 58: Converting the Case of a String
way to modify the individual elements of a container through a conversion function. This saves time because the algorithm has already been written, debugged, and optimized. It also saves time because you can easily extend your conversion functions without rewriting the basic algorithm.
Always make sure that you are using the most efficient code for your application up front. Rather than trying to implement your own algorithms to work with the Standard Template Library, choose to use the ones in the <algorithm> include file as they have been optimized for working with the STL collections.
Implementing the transform Function to Convert Strings
The transform algorithm of the Standard Template Library uses a function created by the user of the algorithm to convert each element of a container in some way. The following steps show you how to create a simple transform function to convert the case of a string, to either upperor lowercase. By looking at the technique and how the code is implemented, you will be able to see how to extend the functionality for your own uses in the future. In order to implement this function, we will need to implement a class which does the work of our transformation. The transform algorithm accepts two iterators and an object to do its work. Let’s take a look at exactly how this is implemented in your own code.
1. In the code editor of your choice, create a new file to hold the code for the source file of the technique.
In this example, the file is named ch58.cpp, although you can use whatever you choose. This file will contain the class definition for your automation object.
2. Type the code from Listing 58-1 into your file.
Better yet, copy the code from the source file on this book’s companion Web site.
LISTING 58-1: CONVERSION CODE FOR THE STL STRING CLASS
#include <string> #include <algorithm> #include <iostream> #include <vector> #include <ctype.h>
using namespace std;
//A function for converting a string to
//lowercase.
string convert_to_lowercase( const string& sIn )
{
//First, save a pointer to the function.
int (*pf)(int)=tolower;
//Next, convert the string. string sOut = sIn;
transform(sOut.begin(), sOut.end(),
sOut.begin(), pf);
return sOut;
}
//A function for converting a string to
//uppercase.
string convert_to_uppercase( const string& sIn )
{
//First, save a pointer to the function.
int (*pf)(int)=toupper;
//Next, convert the string. string sOut = sIn; transform(sOut.begin(), sOut.end(),
sOut.begin(), pf);
return sOut;
}
Testing the String Conversions 351
string strip_leading ( const string& sIn )
{
//Find the first non-white-space character.
int iPos = 0;
while ( iPos < sIn.length() && isspace ( sIn[iPos] ) )
iPos ++;
// Copy the rest of it. string sOut;
for ( int i=iPos; i<sIn.length(); ++i ) sOut += sIn[i];
return sOut;
}
string strip_trailing ( const string& sIn )
{
//Find the last non-white-space character.
int iPos = sIn.length()-1;
while ( iPos >= 0 && isspace( sIn[iPos]
) )
iPos --;
// Copy the rest of it. string sOut;
for ( int i=0; i<=iPos; ++i ) sOut += sIn[i];
return sOut;
}
//This is a utility class that will convert
//a string to lowercase.
class StringConvertToLowerCase |
|
1 |
{ |
|
public:
string operator()(string s)
{
return convert_to_lower_case(s);
}
};
//This is a utility class that will strip
//leading AND trailing white space.
class StringStripSpace
{
public:
string operator()(string s)
{
return strip_leading(strip_ trailing(s));
}
};
This code implements all of the various possible transforms for the string class, and throws in several bonus methods for manipulating the strings (such as stripping the leading and trailing characters). In all cases, we will use the transform method to actually modify the strings or arrays. As the test driver in this technique illustrates, a single string is no more difficult to convert than is an entire string array. The important functionality here is the class we will be using to convert strings to lowercase, which is the StringConvertToLowerCase class shown at 1. The transform function uses this class to convert strings. As you can see, all of the work is done in a single method, the operator() method. This method is called by the transform algorithm to do its work, as we will see shortly.
Testing the String Conversions
After you create a class, 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 list shows 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 ch58.cpp.
352 Technique 58: Converting the Case of a String
2. Type the code from Listing 58-2 into your file.
Better yet, copy the code from the source file on this book’s companion Web site.
LISTING 58-2: THE STRING CONVERSION TEST DRIVER
int main(int argc, char **argv)
{
vector<string> stringArray; if ( argc < 2 )
{
cout << “Usage: ch7_3 string1 [string2 string3...]” << endl;
cout << “Where: string[n] is the string to convert” << endl;
return -1;
}
// First, do them individually.
cout << “Individual Conversions: “ << endl;
for ( int i=1; i<argc; ++i )
{ |
cout << “Input String: “ << argv[i] |
|
|
|
<< endl; |
|
string sLower = |
|
convert_to_lower_case( argv[i] ); |
|
cout << “Lowercase String: “ << |
|
sLower.c_str() << endl; |
|
string sUpper = |
|
convert_to_upper_case( argv[i] ); |
|
cout << “Uppercase String: “ << |
|
sUpper.c_str() << endl; |
|
stringArray.insert( |
} |
stringArray.end(), argv[i] ); 4 |
//Now do the whole array. transform(stringArray.begin(),
stringArray.end(),
stringArray.begin(), StringConvertToLowerCase() );
//Print them out.
cout << endl << “Array Conversions: “ << endl;
vector<string>::iterator iter;
for ( iter = stringArray.begin(); iter != stringArray.end(); ++iter )
cout << “String: “ << (*iter).c_str() << endl;
cout << endl << “Individual String Whitespace Strip Test: “ << endl;
string whiteSpace = “ This is a test “;
string sNoWhite = strip_leading( whiteSpace );
cout << “Stripped String: [“ << sNoWhite.c_str() << “]” << endl;
sNoWhite = strip_trailing( whiteSpace ); cout << “Stripped String: [“ <<
sNoWhite.c_str() << “]” << endl;
transform(stringArray.begin(),
stringArray.end(),
stringArray.begin(), StringStripSpace() );
cout << endl << “Array of Strings Whitespace Strip Test: “ << endl;
for ( iter = stringArray.begin(); iter != stringArray.end(); ++iter )
cout << “String: [“ << (*iter).c_str() << “]” << endl;
return 0;
}
3. Save the source code in your code editor and close the editor application.
4. Compile the source code with your favorite compiler, on your favorite operating system.
5. Run the program on your favorite operating system.
If you have done everything right, you should see the output shown in Listing 58-3 in the console window.
Testing the String Conversions 353
LISTING 58-3: OUTPUT FROM THE STRING CONVERSION TEST
$ ./a.exe “ This is a test” “This is |
|
2 |
|
another test |
“ “ Final Test “ |
|
|
Individual Conversions: |
3 |
||
Input String: |
This is a test |
|
|
Lowercase String: |
this is a test |
|
|
Uppercase String: |
THIS IS A TEST |
|
|
Input String: This is another test |
|
|
|
Lowercase String: this is another test |
|
|
|
Uppercase String: THIS IS ANOTHER TEST |
|
|
|
Input String: |
Final Test |
|
|
Lowercase String: |
final test |
|
|
Uppercase String: |
FINAL TEST |
|
|
Array Conversions: |
|
|
5 |
|
String: |
this is a test |
|
||
String: this is another test |
|
|||
String: |
final test |
|
|
|
Individual |
String Whitespace Strip Test: |
|
||
Stripped String: [This is a test |
] |
|
||
Stripped String: [ |
This is a test] |
|
Array of Strings Whitespace Strip Test: String: [this is a test]
String: [this is another test] String: [final test]
The first line of the output is simply the executable name and the arguments to the program (shown
at |
2). As you can see, we are passing in three |
|||
|
|
|
|
|
arguments. The various transformations are then |
||||
run on each of these arguments. First, we use the |
||||
individual utility functions (as shown at |
|
3) to |
||
convert each of the input strings. This |
simply shows |
|||
|
|
|
that the functions are working properly. The strings are then added to an array (shown at 4 in Listing 58-2). The transform functions are then applied, converting each string to lowercase (shown in the output at 5). Finally, just to show how the transformation can be applied to any string, we use the white space removal functions to change the strings to have no leading or trailing white space.
Keep a library of utility classes around for all of your projects and you will find that you use them automatically — saving time and energy in solving little problems.
59 Implementing a
Serialization
Technique Interface
Save Time By
Understanding interfaces
Understanding serialization
Implementing a serialization interface
Testing the interface
Implementing interfaces can save you loads of time when you are doing the same thing over and over in your application, or even across applications. In addition, it collects all of the code for a given task in one
place, making it quick and easy to change the format of output files or the algorithm used for the functionality of the interface.
If you have ever used Java as a programming language, you’re probably already accustomed to interfaces. Simply put, an interface is a base class from which you can inherit that provides a given service. The C++ language supports interfaces, although they are slightly different in terms of syntax. Unlike a typical base class, interfaces are less concrete and do not generally allow the application to build upon them, but rather to use them to provide a specific service. In C++, an interface is a pure virtual base class that contains multiple pure virtual methods. A pure virtual method, unlike a regular virtual method, must be overridden by a derived class. For example, an interface might allow your class to print itself out, or save itself to a file, or even allocate its memory from some specialized form of hardware space. This technique shows how to implement an important concept — serialization — as an interface. The most important thing about interfaces, and the way in which they will save you the majority of time, is that if you inherit from an interface, you can pass your object to any function or method that works with objects that implement that interface. So, if you have a function that is used to save all sorts of objects, you can pass your object to the function so long as it implements the Save interface.
Essentially, serialization is the capability of an object to write itself to some form of persistent storage. The code that does the job tends to be the same from class to class; the data being written is what changes. Accordingly, serialization lends itself perfectly to the process of creating an interface.
There are two basic steps to implementing an interface in your class: