- •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
63 Creating Debugging
Macros and Classes
Technique
Save Time By
Debugging with the assert macro
Debugging with a logging class
Debugging with DBC (Design by Contract)
Testing your code
When you are debugging an application, having a set of techniques and tools that you can drop into an application will aid you in the process of finding and eliminating bugs. These techniques break
down into two general categories:
Macros used to debug techniques
Classes used in your application to debug the code itself
This technique looks at the sorts of things you can do to build up your own toolbox of debugging techniques and find the ones that work for you. As with most programming, there is no “right” or “wrong” way to debug a problem. There are techniques that work better for some people than others, and it is up to you to discover the ones you like best — and that work most efficiently. By having a library of macros that you can immediately drop into your application, you will save time developing and debugging code.
When it comes to debugging, one size definitely does not fit all. The number of kinds of techniques that people swear by is limited only by the number of programmers using them. To save time for yourself and your application development, you should pick the techniques that work for you and stick with them.
The assert Macro
The first technique we will look at is the assert macro. The assert macro is a simply defined C++ standard macro that evaluates a single argument. If that argument is true, the code continues processing. If the argument is false, the program prints out a diagnostic error message and terminates abruptly. The catch is that the assert macro is only defined when the program is not being compiled in optimized mode. An assert is turned “on” when it is checking values and printing out error messages. Asserts
388 Technique 63: Creating Debugging Macros and Classes
are turned off when they do nothing. In programming parlance, we say that the program is in “debug mode” for assert to work, and in “release mode” if asserts are turned off. Let’s look at an example of how to use the assert macro 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 ch63.cpp, although you can use whatever you choose. This file will contain the class definition for your automation object.
2. Type the code in Listing 63-1 into your file.
Better yet, copy the code from the source file on this book’s companion Web site.
LISTING 63-1: USING THE ASSERT MACRO
#include <assert.h> #include <string.h> #include <stdlib.h>
int func( int v1to10 )
{
assert( v1to10 >= 1 && v1to10 <= 10 ); 1 int divisor = 0;
switch ( v1to10 )
{
case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: case 10:
divisor = v1to10 * 2; break;
}
int retVal = 200 / divisor; return retVal;
}
int main(int argc, char **argv)
{
func(3);
func(11); return 0;
}
In the listing above, our function (func) accepts an integer value. We expect the input value to be in the range of one to ten, inclusive. Values outside of that range will cause a division-by-zero error in the function, so we want to make sure that the user doesn’t supply such a value. The assert statement (shown at the line marked 1) traps for such a condition and exits the program if the value input is outside the specified range.
3. Save the file in the source-code editor and close the editor application.
4. Compile the source-code file with your favorite compiler on your favorite operating system.
5. Run the program on the console window of your favorite operating system.
If you have done everything correctly, you should see the following output on the console window:
$ ./a.exe
assertion “v1to10 >= 1 && v1to10 <= 10” failed: file “ch8_1a.cpp”, line 7
Aborted (core dumped)
The output indicates that the assert function triggered and the program exited. The actual text you see will be dependent on your operating system, compiler, and function library, but it will look similar to this. The important aspect is that the assert macro tells you what file and line the error occurred on and the assertion statement that failed. This will provide you valuable debugging information for determining how the program failed.
Logging 389
If you compile the source code with optimization on, you will see only the core-dumped message (or your operating system’s equivalent of a divide-by-zero error) displayed.
The assert macro is useful during the initial development phase of an application, but it fails to catch run-time errors when the application is released to the user. For this reason, you should never count on assert statements to catch all possible errors.
Logging
The next logical type of debugging technique is the logging approach. Logging is the writing of data persistently in your application in order to facilitate debugging and maintenance. Much like a black box on a crashed airliner, logging records the steps the program took so you can understand how it got into the state it’s in. Unlike some other techniques, logging can be used in either development mode or user-release mode. You can even turn it on or off at run-time — and even during program execution — to see exactly what is going on. The following steps show you how to implement a logging class.
When you can turn your logging capability on or off at will, you can zero in on a problem much more quickly — which allows you to filter out extraneous program information and see only what’s pertinent to the problem. In this way, logging will save you a lot of time when you’re trying to debug a program defect.
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 ch63a.cpp, although you can use whatever you choose. This file will contain the class definition for your automation object.
2. Type the code in Listing 63-2 into your file.
Better yet, copy the code from the source file on this book’s companion Web site.
LISTING 63-2: A LOGGING CLASS
#include <iostream> #include <string> #include <stdlib.h> #include <stdarg.h> #include <fstream>
using namespace std;
class Logger |
|
|
|
{ |
|
|
|
bool |
_bOn; |
|
|
bool |
_bForceFlush; |
|
|
string |
_sMessage; |
|
|
string |
_sFileName; |
|
|
ofstream _file; |
|
|
|
public: |
|
|
|
Logger( void ) |
|
|
|
{ |
|
|
|
_bOn = false; |
|
|
|
_bForceFlush = false; |
|
|
|
} |
|
|
2 |
Logger( const char *strFileName ) |
|
||
{ |
|
|
_sFileName = strFileName; _bOn = false; _bForceFlush = false;
}
Logger( const Logger& aCopy )
{
_sFileName = aCopy._sFileName; _bForceFlush = aCopy._bForceFlush; setOn( aCopy._bOn );
}
virtual ~Logger()
{
Flush(); if ( _bOn )
_file.close();
}
void setOn( bool flag )
(continued)
390 Technique 63: Creating Debugging Macros and Classes
LISTING 63-2 (continued)
{
_bOn = flag; if ( _bOn )
{
_file.open( _sFileName.c_str() );
}
}
bool getOn( void )
{
return _bOn;
}
void setForceFlush( bool flag )
{
_bForceFlush = flag;
}
bool getForceFlush( void )
{
return _bForceFlush;
}
void setFileName ( const char *strFileName )
{
_sFileName = strFileName;
}
string getFileName ( void )
{
return _sFileName;
} |
|
|
|
void Log( const char *strMessage ) |
|
3 |
|
{ |
|
|
|
_sMessage += strMessage; |
|
|
|
_sMessage += ‘\n’; |
|
|
|
if ( _bForceFlush ) |
|
|
|
Flush(); |
|
|
|
} |
|
|
4 |
void LogString( const char *fmt, ... ) |
|
||
{ |
|
|
|
char szBuffer[256]; |
|
|
|
va_list marker; |
|
|
|
va_start( marker, fmt ); |
/* |
|
|
Initialize variable arguments. */ vsprintf(szBuffer, fmt, marker );
_sMessage += szBuffer; _sMessage += ‘\n’;
if ( _bForceFlush ) Flush();
}
void Flush( void )
{
if ( _bOn )
_file << _sMessage << endl; _sMessage = “”;
}
};
The Logger class can be used to do a more extensive information capture within an application. You can log virtually anything you want, regardless of whether the program is compiled in a debug (development) or release (production) mode. Logging can be turned on or off, even at run-time, allowing you to configure your system whenever you want without recompiling the program. The Logger class accepts a filename in which it will write all of its output (see 2), and writes out strings using either a simple string format (see 3) or a more complicated formatted output (see 4). This makes the logging class ideal for inserting into your program and keeping track of important events for either debugging or customer support purposes.
3. Save the source file in your code editor.
Testing the Logger 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 show 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 ch63a.cpp.
2. Type the code from Listing 63-3 into your file.
Better yet, copy the code from the source file on this book’s companion Web site.
Testing the Logger Class 391
LISTING 63-3: THE LOGGER CLASS TEST DRIVER
int main(int argc, char **argv)
{
Logger log(“log.txt”);
//Make the log write things out as it encounters strings,
//to avoid crashes wiping out the log.
log.setForceFlush( true );
// First, see whether the code told us to log anything. for ( int i=0; i<argc; ++i )
{
if ( !strcmp(argv[i], “-log”) )
{
log.setOn(true);
break;
}
}
log.Log(“Program Startup Arguments”); for ( int i=0; i<argc; ++i )
{
log.LogString(“Input Argument %d = %s”, i, argv[i] );
// Prompt for a string, modify it, and then write it out. while ( 1 )
{
printf(“Enter command: “); char szBuffer[ 80 ]; memset( szBuffer, 0, 80 );
if ( gets(szBuffer) == NULL ) break;
log.LogString(“Input String: %s”, szBuffer ); if ( !strlen(szBuffer) )
{
break;
}
string s = “”;
for ( int i=strlen(szBuffer)-1; i>=0; --i ) s += szBuffer[i];
log.LogString(“Output String: %s”, s.c_str() );
}
log.Log(“Finished with application\n”); return 0;
}