- •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 Memory Allocation Tracker |
133 |
All production code should be tested with a memory-leak tool, or run through code like this to see whether memory is being allocated and freed properly, not freed correctly, or freed more than once.
Testing the Memory Allocation
Tracker
In order to see how the allocation tracking code works, it is easiest to create a simple test driver that illustrates the various pieces of the system. Let’s create a simple test program to use the new and delete handlers we have created. The following steps show you how:
LISTING 24-4: MEMORY ALLOCATOR TEST DRIVER
int main(int argc, char **argv)
{
DumpUnfreed(); |
|
|
|
char |
*c = new char[200]; |
|
|
DumpUnfreed(); |
|
3 |
|
char |
*c2 = new char[256]; |
|
|
DumpUnfreed(); |
|
delete c; delete c; DumpUnfreed();
int *x = new int[20]; delete [] x; DumpUnfreed();
Foo *f = new Foo(); delete f;
Foo *af = new Foo[5]; delete [] af;
1.
2.
3.
|
Foo *af1 = new Foo[3]; |
In the code editor of your choice, open the exist- |
delete af1; |
ing file to hold the code for your test program. |
} |
In this example, I named the test program CH 24. |
|
Type the code from Listing 24-4 into your file. |
4. Compile the source file, using your favorite |
|
|
Better yet, copy the code from the source file on |
compiler on your favorite operating system. |
|
|
this book’s companion Web site. |
If you run the resulting executable, the program |
|
|
Save the source code and close your code editor. |
should give you the output shown in Listing 24-5. |
|
LISTING 24-5: OUTPUT FROM THE MEMORY TRACKING PROGRAM
$ ./a.exe
----------------------- Allocations ----------------------
-----------------------------------------------------------
Total Unfreed: 0 bytes
Array operator new called
----------------------- |
Allocations ---------------------- |
(0) ADDRESS a050648 |
Size: 200 unfreed |
-----------------------------------------------------------
Total Unfreed: 200 bytes
Array operator new called |
|
|||
----------------------- |
|
|
Allocations |
---------------------- |
(0) |
ADDRESS |
a050648 |
Size: 200 |
unfreed |
(1) |
ADDRESS |
a050770 |
Size: 256 |
unfreed |
-----------------------------------------------------------
(continued)
134 Technique 24: Defining Your Own new and delete Handlers
LISTING 24-5 (continued)
Total Unfreed: 456 bytes
Basic operator |
delete called |
||
Basic operator |
delete called |
||
Unable to find |
allocation for delete [168101448] |
||
----------------------- |
|
Allocations ---------------------- |
|
(1) |
ADDRESS a050770 |
Size: 256 unfreed |
|
----------------------------------------------------------- |
|||
Total Unfreed: |
256 bytes |
|
|
Array operator |
new called |
|
|
Array operator |
delete called |
||
----------------------- |
|
Allocations ---------------------- |
|
(1) |
ADDRESS a050770 |
Size: 256 unfreed |
|
----------------------------------------------------------- |
|||
Total Unfreed: |
256 bytes |
4 |
|
Basic operator |
new called |
|
|
Foo Constructor called |
|
||
Foo Destructor |
called |
|
|
Basic operator |
delete called |
||
Array operator |
new called |
|
|
Foo Constructor called |
|
||
Foo Constructor called |
|
||
Foo Constructor called |
|
||
Foo Constructor called |
|
||
Foo Constructor called |
|
||
Foo Destructor |
called |
|
|
Foo Destructor |
called |
|
|
Foo Destructor |
called |
|
|
Foo Destructor |
called |
|
|
Foo Destructor |
called |
|
|
Array operator |
delete called |
||
|
|
|
|
There are a lot of interesting things to take out of this technique. First, it gives you a better appreciation of what goes on behind the scenes in a typical C++ program. Second, you can see right away how the allocations and de-allocations are being handled and where the leaks are. In our example, we can see at the end of the program that we have a single memory leak of 256 bytes (at 4 in Listing 24-5). Note that we print out the current state of the program several times, so it is only the last display that indicates the leak at the end of the program. The
others are left in to illustrate how the program is allocating memory. It’s obvious, from looking at the program code, that this occurs for the c2 allocation (see 3 in Listing 24-4). We simply need to add a delete call for the character pointer and all will be copacetic.
The other interesting thing to note in this technique is which C++ new operator is called when. If you allocate a character pointer, for example, it calls the array version of the new operator. This situation is
Testing the Memory Allocation Tracker |
135 |
counterintuitive — after all, you’re allocating a single character pointer — but it makes sense if you really think about it. It’s an array of characters that we happen to treat as a single string, which gives us the array version of the new operator. Likewise, when we allocate a single object, it calls the basic operator for the new allocation.
Before we leave this concept, there’s one more potential mess worth looking at. Try adding the following code to the end of your driver application:
Foo *af1 = new Foo[3]; delete af1;
If you compile this snippet of code at the end of your driver program, and then run the program, you will see the following output:
Array operator new called Foo Constructor called Foo Constructor called Foo Constructor called Foo Destructor called
Basic operator delete called
Unable to find allocation for delete [168101480]
Looking at the output, you will notice that the constructor for the class was called three times, for the three objects we created. The destructor, however, was only called once. Worse, because the block of memory allocated is actually three separate objects, our deletion routine couldn’t find it to delete it.
Moral: Always call the right version of delete for the corresponding version of new.
The new new operator
In C++, there is another version of the new operator called the new in place operator. This operator invokes the constructor for an object and sets it to point to a specific block of memory that is passed into it. If you are implementing a system that uses an embedded processor, where you cannot “really” allocate memory, or if you have an object pool, you might consider such a choice.
Add a memory tracker to every application you create. You can conditionally compile in the code to see how things are going at any stage of the application-development phase, and can use the resulting reports for quality assurance.
25 Implementing
Properties
Technique
Save Time By
Understanding properties in C++
Implementing a
Property class
Testing your Property class
If you are accustomed to programming in the “new” languages, such as Java or C#, you are probably already familiar with the concept of properties. Essentially, properties are public elements of a class that
have their own methods for getting and setting their values. Unlike traditional public values, however, a property cannot be accessed directly by the programmer — even though it looks like it can. A property has set and get functions that are invoked when you attempt to write or read to the property values. C++ has no direct implementation of properties in the language. This is really a shame, because properties save a lot of time for the end user by making it easier to read and write data values within the class.
For example, suppose you have a class named Foo that contains a property called age. This property can be set by the application developer, but only to values within the range of (say) 18 to 80. Now, in a “standard” C++ application, you could define the class with a public member such as in the following:
class Foo
{
public:
Foo()
{
}
int age;
};
If you had such a class, you could then write application code to directly set the age property, like this:
int main()
{
Foo f; f.age = 22;
}