- •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
418 Technique 66: Documenting the Data Flow
LISTING 66-1 (continued)
_previous.push( sold );
}
else
{
State sold(name, “”); _previous.push( sold );
}
char szBuffer[20]; sprintf(szBuffer, “%d”, value ); _props[name] = szBuffer;
}
string getProperty( const char *name )
{
return _props[ name ];
}
}
// Pop off the last change State s = _previous.top();
_previous.pop();
// Apply it _props[s.getName()] = s.
getValue();
}
Here, because we’re tracking all changes to the object anyway, undoing one of those changes is trivial. Adding this sort of code to the system at the outset — rather than trying to build it in later — makes for a very robust system that’s also easy to debug.
4. Save the source code in your code editor.
Okay, there’s nothing particularly special about this code — it simply allows you to add new properties to an object, modify them as you see fit, and retrieve them. It also keeps track of all of the changes to a given object, which would allow you to log those changes, or even implement an undo system. To give you an idea of how simple it would be to add functionality to a system based on this object, the next step adds an
undo method for the Properties object. The
Properties class, shown at 1, keeps track of a |
|
list of properties for other objects. |
Within the |
class, the State class (shown at |
2) is used to |
maintain a list of the various values for each
property. When a value is changed, a new State object is created with the old value and stored (see 3). This will allow us to implement undo very simply.
3. Add the following code to the class listing:
void undo()
{
if ( _previous.empty() ) return;
Testing the Properties Class
After you create a class, you should create a test driver that not only ensures your code is correct, but also shows people how to use your code. The following steps tell you how:
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 ch66.cpp.
2. Type the code from Listing 66-2 into your file.
Better yet, copy the code from the source file on this book’s companion Web site.
LISTING 66-2: THE PROPERTY TEST PROGRAM
int main()
{
Properties |
p; |
|
|
p.setProperty( “x”, 12 ); |
|
4 |
|
cout << “x |
= “ << p.getProperty |
|
|
|
|
|
|
(“x”).c_str() << endl;
Testing the Properties Class 419
p.setProperty( |
“x”, 13 ); |
|
5 |
|
cout << |
“x = “ |
<< |
|
|
p.getProperty(“x”).c_str() << endl; |
6 |
|||
p.undo(); |
|
|
||
cout << |
“x = “ |
<< |
|
p.getProperty(“x”).c_str() << endl;
}
If you have done everything properly, you should see the following output from the program on the console window:
$ ./a.exe x = 12
x = 13 x = 12
The test program simply creates a Properties object and adds a new property called x to it
(shown at 4). We print out the value of that |
|
property, then |
change it 5. The value is then |
printed out again to verify the change. At this
point, we undo the last change for the Properties object by using the undo method at 6. We would then expect the value of x to be its previous value, 12, rather than the current value of 13.
3. Save the source code in the source-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 console.
As we expected, the value of x changes from the last set value, 13, to its previous setting of 12 due to the undo function call. The undo functionality, besides being useful in a program by itself, also shows you how to track changes to data within the program. This ability will make it very simple to debug applications by seeing exactly how things changed over time.
As you can see, the system properly stores the information for the properties and easily implements the undo system. This type of object could easily be dropped into a “regular” object to replace the entire member variable list.
Tracking and documenting the data flow within an application is the single most important thing you can do for programmers, maintainers, debuggers, and customer support personnel. Data flow is what the user cares about and the QA department uses to validate your system. By making it easy to see what changes and when it happens, you save yourself immense amounts of time later on in the development process.
67 Creating a Simple
Locking Mechanism
Technique
Save Time By
“Locking” functionality in an application
Creating a locking mechanism
Testing the locking mechanism
From time to time, programmers must “lock out” the functionality of a given application. There are many possible reasons for this necessity: If you’re running a multithreaded application, for example, you
need to keep multiple threads from hitting the same function at the same time. Or perhaps you’re writing an application in which a resource (such as a hardware device) can be accessed only at certain times.
“Locking” a program means denying the program access into a given block of code until a certain condition occurs. Not unlike a finite-state machine, a lock mechanism can force a system into certain transitions (movements from one state to another) only when they are ready to happen — which ensures a predictable outcome and avoids unforeseen circumstances. Locking mechanisms save time for the developer by reducing hard to reproduce errors and problems that can only be tested in multi-user environments.
There are many ways to implement locking in an application. Most operating systems provide heavyweight critical-section handlers that can be used to lock only small pieces of code at the hardware level. These mechanisms, however, are intended only for serious multithreading; they impose too much overhead in terms of processing time, memory required, and code required, for the average application to utilize. What is really needed is a more lightweight system — with little or no overhead — that you can use to lock global resources within your application code at run-time. Filling that need is the purpose of this technique.
Portability is one big advantage of implementing and employing your own locking mechanism instead of a system-level lock. Using your own locking mechanism allows your code to port easily from system to system without requiring extensive rewrites (often necessary when you use a new compiler or operating system). Even if you choose to use the underlying system support to lock your application, you should wrap that functionality in your own class so it’s the only place you have to make changes when you move to a new operating system, compiler, or version of the library code.
Creating the Locking Mechanism 421
Creating the Locking
Mechanism
Giving your code the appropriate locking functionality is pretty straightforward. The following steps show how to create a class that does the job:
1. In the code editor of your choice, create a new file to hold the code for the implementation of source file.
In this example, the file is named ch67.cpp, although you can use whatever you choose.
2. Type the code in Listing 67-1 into your file.
Better yet, copy the code from the source file on this book’s companion Web site.
LISTING 67-1: THE SIMPLE LOCKING-MECHANISM CLASS
#include <string> #include <vector>
using namespace std;
class LockException
{
string _msg; public:
LockException(void)
{
_msg = “Lock Exception”;
}
LockException( const char *msg )
{
_msg = “Lock Exception: “; _msg += msg;
}
LockException( const LockException& aCopy )
{
_msg = aCopy._msg;
}
const char *Message(void)
{
return _msg.c_str();
}
void setMessage( const char *msg )
{
_msg = msg;
}
};
class Lock
{
private:
static bool _bLock; bool _isLocked;
public:
Lock(void)
{
_isLocked = false;
}
Lock( const Lock& aCopy )
{
_isLocked = aCopy._isLocked;
}
virtual ~Lock()
{
unLock();
}
bool setLock()
{
if ( !_isLocked )
{
if ( _bLock == false )
{
_bLock = true; return true;
}
}
return false;
}
bool isLocked(void)
{
return _bLock;
}
void unLock( void )
{
if ( _isLocked )
{
if ( _bLock == true )
{
_bLock = false;
}
}
}
};
bool Lock::_bLock=false;
1
2