- •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 Flow Trace System 379
debugFlowTracerManager *debugFlowTracerManager::m_Instance = NULL;
void debugFlowTracer::AddFlow()
{
} |
debugFlowTracerManager::Instance()->addFlow( *this ); |
2 |
void debugFlowTracer::RemoveFlow()
{
debugFlowTracerManager::Instance()->removeFlow( *this );
}
The purpose of the debugFlowTracerManager class is to keep track of the various flows within the system. Because a flow can really start and end anywhere in the source code of the application, we need a single place to store all of them. This allows us to print them out at the point that gives us the best view of how the processing went. The flow manager contains a single method, Instance (shown at 1), to return an instance of the class. Otherwise, you will notice that the constructors are all private, so that users cannot create an instance of the class. This ensures that there is only a single object of this class in any given application.
Testing the Flow Trace System
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. Append the code from Listing 62-3 into your source file.
Better yet, copy the code from the source file on this book’s companion Web site.
LISTING 62-3: THE FLOW TRACE TEST PROGRAM
void func_3()
{
debugFlowTracer flow(“func_3”);
}
void func_2()
{
debugFlowTracer flow(“func_2”); func_3();
}
void func_1()
{
debugFlowTracer flow(“func_1”); func_2();
}
int main(int argc, char* argv[])
{
debugFlowTracer mainFlow(“main”); func_1();
func_2(); func_3(); return 0;
}
The test driver is constructed to illustrate the way in which the flow manager works. Note that we simply define a debugFlowTracer object in each of the functions in the code, and then use the functions as we would normally. The debugFlowTracer
380 Technique 62: Building Tracing into Your Applications
object attaches itself to the the instance of the manager (shown back in Listing 62-2 at 2) and adds itself as a flow when it is created. The manager keeps track of all of the flows, printing them out for diagnostic purposes as they are created.
2. Save the source code in the source-code editor and close the source-editor application.
3. Compile the application using your favorite compiler on your favorite operating system.
4. Run the application on your favorite operating system.
If you have done everything properly, you should see the following output in the console window of your operating system:
$ ./a |
|
|
Flow [main]: |
|
|
Flow [func_1]: |
|
|
Flow [func_2]: |
|
|
Flow [func_3]: |
|
|
Flow [func_2]: |
|
3 |
func_3 |
|
|
Flow [func_3]: |
|
As you can see, the program indicates what flows are running in the application, and how it got from one point to another. The names of the flows are printed within the square brackets as they are created. When a flow goes out of scope (when the function ends) the sub-flows (calls within that function) are printed out. You can see that in our case, only one of the functions called calls another function. This is shown in the listing at the line marked with 3, indicating where func2 called func3. This gives us a good example of tracing and shows us how func3 was called throughout the program.
Adding in Tracing
After the Fact
after it has been designed, coded, and released. After all, you say, if it was up to you, the code would have been in there in the first place. Why should you pay for the mistakes of the developers before you? The answer is, because that’s the way the world works. Someone comes along, writes a bunch of ugly, unmaintainable code, and then moves on to do it again someplace else. You get to move in and fix the disaster that was left behind.
Fortunately, this isn’t really one of those times. It is possible to add in flow tracing after the fact, even automating the process. Let’s create a simple little application that will do just that.
1. In the code editor of your choice, create a new file to hold the code for the implementation of the source file.
In this example, the file is named ch62a.cpp, although you can use whatever you choose.
2. Type the code from Listing 62-4 into your file.
Better yet, copy the code from the source file on this book’s companion Web site.
A word of warning
Before you use the insertion program, here are a few things you need to know about it:
Consider it a jumping-off point for your own creations. It is not intended to be used in a production environment. Using it that way is likely to confuse the program; its interaction with complex programs may require you to update the output. (That is why it does not overwrite your original source.)
It does not handle all cases. Inline code in a class will not be detected and updated.
The code will sometimes detect a function or method when one does not exist. For example, the code will sometimes make this mistake in a macro.
One of the more annoying things in the software world is being told to add something to your application
The insertion program will insert tracing objects in most, but not all, of your application functions with the exceptions listed in the notes above. Use it in good health.
Adding in Tracing After the Fact 381
LISTING 62-4: A UTILITY PROGRAM TO INSERT TRACING INTO AN EXISTING FILE
#include <string> #include <ctype.h>
void eat_line( FILE *fp, std::string& real_line )
{
// Just read to the end of the line. while ( !feof(fp) )
{
char c = fgetc(fp); real_line += c;
if ( c == ‘\n’ ) break;
}
}
void eat_comment_block( FILE *fp, std::string& real_line )
{
char sLastChar = 0;
// Find the matching comment-close character. while ( !feof(fp) )
{
char c = fgetc(fp); real_line += c;
if ( c == ‘/’ && sLastChar == ‘*’ ) break;
sLastChar = c;
}
}
std::string get_line( FILE *fp, std::string& real_line )
{
std::string sLine = “”; char sLastChar = 0; while ( !feof(fp) )
{
// Get an input character. char c = fgetc(fp);
real_line += c;
// Check for pre-processor lines.
if ( c == ‘#’ && (sLastChar == 0 || sLastChar == ‘\n’) )
{
eat_line( fp, real_line ); continue;
}
(continued)
382 Technique 62: Building Tracing into Your Applications
LISTING 62-4 (continued)
// Check for comments. if ( c == ‘/’ )
{
sLastChar = c; c = fgetc(fp); real_line += c;
if ( c == ‘/’ )
{
eat_line( fp, real_line ); sLastChar = 0;
continue;
}
else
if ( c == ‘*’ )
{
eat_comment_block( fp, real_line ); sLastChar = 0;
continue;
}
else
{
sLine += sLastChar;
}
}
// Need to skip over stuff in quotes.
if ( c != ‘\r’ && c != ‘\n’ )
{
//Here it gets weird. If the last character was
//a parenthesis, we don’t want to allow white space. if ( sLastChar != ‘)’ || !isspace(c) )
sLine += c; else
continue;
}
// A line terminates with a {, a }, or a ; character. if ( c == ‘;’ || c == ‘{‘ || c == ‘}’ )
break;
sLastChar = c;
}
Adding in Tracing After the Fact 383
return sLine;
}
std::string parse_function_name( std::string& sLine )
{
std::string sName = “”;
//First, find the opening parenthesis. int sPos = (int)sLine.find(‘(‘);
//Skip over everything that is a space before that. for ( int i=sPos-1; i>=0; --i )
if ( !isspace(sLine[i]) )
{
sPos = i; break;
}
//Now everything backward from that is the name, until
//we hit either the beginning of the line or white space. int sStartPos = 0;
for ( int i=sPos; i>=0; --i )
{
if ( isspace(sLine[i]) ) break;
sStartPos = i;
}
sName = sLine.substr( sStartPos, sPos-sStartPos+1 );
return sName;
}
void ProcessFile( FILE *fp, FILE *ofp )
{
std::string real_line;
while ( !feof(fp) )
{
real_line = “”;
std::string sLine = get_line( fp, real_line );
// Check for functions/methods.
(continued)
384 Technique 62: Building Tracing into Your Applications
LISTING 62-4 (continued)
//Output the “real” line, and then (if we need to) the
//information we need to embed.
fprintf(ofp, “%s”, real_line.c_str() ); if ( sLine[sLine.length()-1] == ‘{‘ && sLine[sLine.length()-2] == ‘)’ )
{
std::string sName = parse_function_name( sLine );
fprintf(ofp, “\n\tdebugFlowTracer flow(\”%s\”);\n”, sName.c_str());
}
}
}
int main(int argc, char* argv[])
{
if ( argc < 2 )
{
printf(“Usage: cppparser filename [filename...]\n”); exit(1);
}
for ( int i=1; i<argc; ++i )
{
FILE *fp = fopen(argv[i], “r”); if ( fp == NULL )
{
printf(“Error: Unable to process file %s\n”, argv[i] ); continue;
}
std::string sOut = std::string(argv[i]) + “.tmp”; FILE *ofp = fopen(sOut.c_str(), “w”);
if ( ofp == NULL )
{
printf(“Error: Unable to create output file %s\n”, sOut.c_str() ); continue;
}
//Process this file ProcessFile( fp, ofp );
//Finish it up fclose(fp); fclose(ofp);
}
return 0;
4
5
Adding in Tracing After the Fact 385
There is nothing magical about Listing 62-4. The code takes an input file and writes it out to an output temporary file, appending certain information in the file as it parses the text within the input file. In our case, the code is looking for
opening braces (shown at |
4) to indicate the |
beginning of a function. When one is encountered, |
it parses the function name (shown at 5) and |
|
writes a debugFlowTracer object definition |
into |
the output file. This creates an automated system for inserting flow tracing into an existing source file.
3. Save the source code in the source-code editor.
4. Create a new file in the source-file editor to use as a test input to the insertion program.
This file simply acts as test input for the program; it doesn’t really have to do anything on its own. For now, just create a file that contains some functions and some class methods.
In this example, the file is named temp.cpp, although you can use whatever you choose.
5. Type the code from Listing 62-5 into your new file.
Better yet, copy the code from the source file on this book’s companion Web site.
LISTING 62-5: A TEMPORARY PROGRAM TO ILLUSTRATE TRACE
INSERTION INTO AN EXISTING FILE
#include <stdio.h> #include <string> #include <iostream>
int func1()
{
printf(“This is a test\n”);
}
void func2()
{
printf(“This is another test\n”);
}
class Foo
{
public:
Foo();
virtual ~Foo();
};
Foo::Foo(void)
{
}
Foo::~Foo(void)
{
}
int main()
{
Foo x;
}
Don’t spend any time studying the code in Listing 62-5; its sole purpose is to provide an input file to test out the parser. If it works properly, we will see an output file which contains debugFlowTracer objects in func1, func2, and the constructor and destructors for the Foo object and the main function.
6. Compile the insertion program with your favorite compiler on your favorite operating system.
7. Run the program on your favorite operating system.
If you have done everything right, you should see a file called temp.cpp.tmp created that contains the following text in it:
#include <stdio.h> #include <string> #include <iostream>
int func1() |
|
{ |
|
debugFlowTracer flow(“func1”); |
6 |
386 Technique 62: Building Tracing into Your Applications
printf(“This is a test\n”); |
} |
} |
|
|
Foo::~Foo(void) |
void func2() |
{ |
{ |
debugFlowTracer flow(“Foo::~Foo”); |
debugFlowTracer flow(“func2”); |
|
|
} |
printf(“This is another test\n”); |
|
} |
int main() |
|
{ |
class Foo
{
public:
Foo();
virtual ~Foo();
};
Foo::Foo(void)
{
debugFlowTracer flow(“Foo::Foo”); 7
debugFlowTracer flow(“main”);
Foo x;
}
Note that the program properly inserted all of the flow-tracing objects into the existing file. The lines marked 6 and 7, for example, show you that the output is exactly what we wanted and expected.