- •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
Implementing the Serialization Interface |
355 |
You must create the actual interface class and identify all areas in which you need input from the derived class. At this stage, you’re implementing all the functionality for the interface class — creating it as if it were a main class for your application.
After you’ve created the interface class, you set up your derived class to inherit from it (and to implement any virtual functions the interface requires).
Implementing the Serialization
Interface
The most popular interface is the serialization interface. This interface, used by many of the popular class libraries, such as MFC, allows an object to be written to persistent storage (such as a file) so long as it implements a consistent interface. In this example, we will explore how to create a serialization interface and apply that interface to a given class or classes.
1. In the code editor of your choice, create a new file to hold the code for the interface definition of the technique.
In this example, the file is named ch59.h, although you can use whatever you choose. This file will contain the class definition for your serialization object.
2. Type the code from Listing 59-1 into your file.
Better yet, copy the code from the source file on this book’s companion Web site.
LISTING 59-1: THE SERIALIZATION INTERFACE CODE
#ifndef _SERIALIZE_H_ #define _SERIALIZE_H_
#include <string> #include <vector>
#include <iostream> #include <fstream>
using namespace std;
class SerializeEntry
{
string _name; string _value;
public:
SerializeEntry(void)
{
}
SerializeEntry( const char *name, const char *value )
{
setName( name ); setValue( value );
}
SerializeEntry( const char *name, int iValue )
{
setName( name ); setValue( iValue );
}
SerializeEntry( const char *name, double dValue )
{
setName( name ); setValue( dValue );
}
SerializeEntry( const SerializeEntry& aCopy )
{
setName( aCopy.getName().c_str() ); setValue( aCopy.getValue().c_str()
);
}
SerializeEntry operator=(const SerializeEntry& aCopy)
{
setName( aCopy.getName().c_str() ); setValue( aCopy.getValue().c_str()
);
return *this;
}
(continued)
356 Technique 59: Implementing a Serialization Interface
LISTING 59-1 (continued)
void setName( const char *name )
{
if ( name ) _name = name;
else
_name = “”;
}
void setValue( const char *value )
{
if ( value ) _value = value;
else
_value = “”;
}
void setValue( int iValue )
{
char szBuffer[ 20 ]; sprintf(szBuffer, “%d”, iValue ); setValue ( szBuffer );
}
void setValue( double dValue )
{
char szBuffer[ 20 ]; sprintf(szBuffer, “%lf”, dValue ); setValue ( szBuffer );
}
string getName(void) const
{
return _name;
}
string getValue(void) const
{
return _value;
}
};
class Serialization
{
private:
long _majorVersion; long _minorVersion;
protected:
virtual bool getEntries( vector< SerializeEntry >& entries )
{
return true;
}
virtual string getClassName()
{
return “None”;
} |
|
public: |
|
Serialization(void) |
|
{ |
|
_majorVersion = |
0; |
_minorVersion = |
0; |
} |
|
Serialization( long |
major, |
{ long minor ) |
2 |
_majorVersion = |
major; |
_minorVersion = |
minor; |
}
Serialization( const Serialization& aCopy )
{
_majorVersion = aCopy._majorVersion; _minorVersion = aCopy._minorVersion;
}
Serialization operator=( const Serialization& aCopy )
{
_majorVersion = aCopy._majorVersion; _minorVersion = aCopy._minorVersion; return *this;
}
// Accessors
void setMajorVersion( long major )
{
_majorVersion = major;
}
long getMajorVersion( void )
{
return _majorVersion;
}
void setMinorVersion( long minor )
{
_minorVersion = minor;
}
long getMinorVersion( void )
{
return _minorVersion;
} |
|
|
// Functionality |
|
1 |
bool write( const char *strFileName ) |
|
|
{ |
|
Implementing the Serialization Interface |
357 |
//Note the call to the virtual method. This must
//be overridden in the derived class code.
vector< SerializeEntry > |
entries; |
||
getEntries( entries |
); |
|
3 |
if ( entries.size() |
== |
0 |
) |
return false; |
|
|
|
//Try opening the output file. ofstream out( strFileName,
ofstream::out | ofstream::app ); if ( out.fail() )
return false;
//Write out the class name.
out << “<” << getClassName() << “>”
<<endl;
//Write out the version informa-
tion.
out << “\t<VERSION>” << _majorVersion << “:” << _minorVersion << “</VERSION>” << endl;
vector< SerializeEntry >::iterator iter;
for ( iter = entries.begin(); iter != entries.end(); ++iter )
{
out << “\t<” << (*iter).getName().c_str() << “>” << endl;
out << “\t\t” << (*iter).getValue().c_str() << endl;
out << “\t</” << (*iter).getName().c_str() << “>” << endl;
}
out << “</” << getClassName() << “>” << endl;
return true;
}
};
#endif
This code saves a given class to an output stream in standard XML format. It would be simple enough, of course, to modify the class to output in some other format, but for simplicity we will use XML. The important member of the class is the write method, shown at 1. This method writes out the members of a class in XML format, using the member variables of the class to determine the output file and version information. Note that the class keeps track of its own version information so that you can use it to determine whether a persistent version of the class is of the proper version for your application. The version information is defined in the constructor, as shown at 2.
Take a look at the write method; it appears that the code does everything you would expect a
serialization object to do. The important part |
|
of the function is shown at 3, which is a call |
|
to a virtual method called getEntries |
. This |
method, which must be overridden by any class that uses this interface, returns the individual member variables of the class as a string array. After this method is created for your class, the rest of the functionality of the serialization interface will work no matter what the content of the derived classes.
3. Save the source file and close the file in the code editor.
As you can see, the serialization class does most of the work by itself. It requires the implementation of only two virtual methods:
The getElements method returns all internal elements of the class that you want saved.
The getClassName method returns the name of the class to use as the root of the XML tree that receives the element data we write out.
358 Technique 59: Implementing a Serialization Interface
In addition, you can specify major and minor versions for the serialized file, so that if you wish to import existing serialized files, you will later on be able to import data created by older versions of the source code and properly default missing values if new ones have been added in the meantime.
If you are persistently storing data for a class, make sure that you store version information with the data, so you can always know exactly when the data was written and what might be missing or superfluous. As classes change over time, member variables are added or removed. The data stored in a serialized version of older classes, therefore, may have additional or missing data. If you know what version of the class created the serialized data, you will know what data to map into your current member variable set.
Testing the Serialization
Interface
After we have defined the serialization interface, the next step is to implement that interface for a real class. This allows you to see how the serialization process is used and how much time the interface concept will save in future class implementations. Let’s take a look at creating a class with members that need to be stored persistently.
1. In the code editor, create a new file to hold the code for the test class of the technique.
In this example, the file is named ch59.cpp, although you can use whatever you choose. This file will contain the test code for the technique.
2. Type the code from Listing 59-2 into your file.
Better yet, copy the code from the source file on this book’s companion Web site.
LISTING 59-2: TESTING THE SERIALIZATION INTERFACE
#include “serialize.h” |
|
class MyClass |
: public Serialization |
{ |
|
private: |
|
int |
_iValue; |
string |
_sValue; |
double |
_dValue; |
protected: |
|
virtual bool getEntries( vector |
|
4 |
|
< SerializeEntry >& entries ) |
|
||
{ |
entries.insert( entries.end(), |
|
|
|
|
|
|
|
SerializeEntry( “iValue”, |
|
5 |
|
_iValue ) ); |
|
|
|
entries.insert( entries.end(), |
|
|
|
SerializeEntry( “sValue”, |
|
|
|
_sValue.c_str() ) ); |
|
|
|
entries.insert( entries.end(), |
|
|
|
SerializeEntry( “dValue”, _dValue |
|
|
} |
) ); |
|
|
|
|
|
|
virtual string getClassName( void ) |
|
|
|
{ |
return “MyClass”; |
|
|
} |
|
|
|
|
|
|
|
public: |
|
|
|
MyClass(void) |
6 |
||
{ |
: Serialization(1,0) |
_iValue = 0; _dValue = 0.0; _sValue = “”;
}
MyClass(int iVal, double dVal, const char *sVal)
: Serialization(1,0)
{
_iValue = iVal; _dValue = dVal; _sValue = sVal;
}
virtual ~MyClass()
{
Save();
}
virtual void Save()
{
Testing the Serialization Interface |
359 |
write( “MyClass.xml” );
}
};
int main()
{
MyClass m1(1,2.0,”One”);
return 0;
}
This class simply implements a collection of data, with a string value, an integer value, and a floating point (double) value. The important work takes place in the getEntries virtual function, which builds an array of elements to output for serialization. This method, shown at 4, overrides the serialization interface method
and will be used for output. After this method is overridden, the remainder of the code works as expected within the interface. Note the use of the SerializeEntry class (shown at 5) to hold the data for each element. Because this class can be derived to utilize other data types, the interface concept works even into the future.
3. Save the source code as a file in your 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, the program will create an output file called MyClass.xml in your operating system’s file system. This file should contain the following output after the application is run:
$ cat MyClass.xml <MyClass>
<VERSION>1:0</VERSION>
<iValue>
1
</iValue>
<sValue>
One </sValue> <dValue>
2.000000
</dValue>
</MyClass>
In the output, you can see that all of the data elements specified in the MyClass class have been output via the serialization interface. This shows that our code works as we specified. Also note that the version information, which we specified in the constructor for the MyClass class (shown at 6), is output in the persistent file, so that we can utilize it if we add new members to the MyClass class.
As you can see, the serialization class did exactly what it was supposed to do. In addition, note that the code for the serialization class has virtually no relation to the class from which it is called. This lack of specialization allows us to easily reuse the class as an interface to as many other classes as we want — allowing each of them to serialize the data efficiently.
Also note that if you were suddenly instructed to change the output to a format other than XML, the code needs adjustment in only one place. This saves you time, avoids mistakes, and is in keeping with the best strengths of object-oriented programming.
60 Creating a Generic
Buffer Class
Technique
Save Time By
Understanding buffer overflow errors
Preventing buffer overflow errors from being exploited as a security risk
Creating a Buffer class that deals with these errors
Testing your class
Buffer overlows occur in a majority of C or C++ programs. Imagine, for a moment, reading data from an input file. The typical C or C++ code might look something like this:
char szBuffer[80]; int nPos = 0; while ( !eof(fp) )
{
szBuffer[nPos] = fgetc(fp); if ( szBuffer[nPos] == ‘|’ )
break;
nPos++;
}
This routine is supposed to read in a string from the file, reading until it hits a pipe (|) character or the end of the file. But what happens if the string is longer than 80 characters? Well, in that case, the routine continues to read in the string information, overwriting the memory locations that follow the szBuffer variable. We refer to this problem as a buffer overflow. What information is stored in those memory locations? Well, that’s hard to say: Maybe there is nothing important there — or maybe an important return address for a function is being overwritten. In the former case, you might never see a problem. In the latter case, the program could easily crash, exposing a serious vulnerability to the outside world.
Problems like this, known as buffer overflow, are really quite widespread in the software-development world — but unless they’re causing major issues (or haven’t been discovered yet), programmers tend to overlook them. Even so, buffer overflow is considered the number-one security problem in software today. Depending on the application, a buffer overflow can crash the application, or simply destroy some vital part of the security wall that prevents an outside user from modifying data within a program. So why are we not doing something about it?