- •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
Choosing Initialization Instead of Assignment |
413 |
//Construct the object. fileHandle fh(strName);
//This will check to see if the file already exists.
if ( !fh.Exists() ) return –1;
//Now, we can do the “expensive” operation of
//opening and reading the file. if ( fh.Open() == false )
return –1;
//Read the file...
}
In this example, we first check all input for validity. If it isn’t valid, there is no cost to the fileHandle object being created. If the input is valid, we then pass the validation onto that object. First, we simply set the filename into the object and see whether that file exists. Then we try to open the file — which will buffer the data for it and do the overhead (that is, getting the operating system to open the file). Only then, if all those things work, do we actually read the file, which costs the most time.
Choosing Initialization
Instead of Assignment
The final optimization technique to look at is initializing data (rather than assigning data in constructors for classes). Under normal circumstances, the member data for a class is first constructed (using default constructors), and then assigned values based on input to the constructor (or defaults provided by the programmer). The problem with this approach is that the initialization is really done twice — first in the constructor and then in the assignment. This is wasteful, and leads to program slowdowns.
To see how to make this improvement, follow these steps:
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 ch65b.cpp, although you can use whatever you choose.
2. Type the code from Listing 65-3 into your file.
Better yet, copy the code from the source file on this book’s companion Web site.
LISTING 65-3: INITIALIZING VERSUS ASSIGNING
#include <iostream> #include <string>
using namespace std;
class Point
{
private: int _x; int _y;
public:
Point(void)
{
cout << “Point: void constructor called” << endl;
_x = 0; _y = 0;
}
Point( int x, int y )
{
cout << “Point: full constructor called” << endl;
_x = x; _y = y;
}
Point( const Point& p )
{
cout << “Point: copy constructor called” << endl;
(continued)
414 Technique 65: Optimizing Your Code
LISTING 65-3 (continued)
_x = p._x; _y = p._y;
}
Point& operator=( const Point& p )
{
cout << “Point: operator= called” << endl;
_x = p._x; _y = p._y;
return *this;
}
int& X()
{
return _x;
}
int& Y()
{
return _y;
}
}; |
|
|
|
|
class Line |
|
|
|
|
{ |
|
|
|
|
Point |
_p1; |
|
|
|
Point |
_p2; |
|
|
|
public: |
|
|
|
|
Line(void) |
|
|
|
|
{ |
|
|
|
|
} |
|
|
|
|
Line( |
int x1, int x2, int y1, |
|
|
9 |
int |
y2 ) |
|
||
: _p1(x1,y1), |
|
|||
|
10 |
|||
|
_p2(x2,y2) |
|
|
{
}
Line( const Point& p1, const Point &p2 )
{
_p1 = p1; _p2 = p2;
}
Point& TopLeft()
{
return _p1;
return _p2;
}
};
int main()
{
//First, create some points. Point p1(0,0);
Point p2(10,10);
//Now create some lines. cout << “Line 1: “ << endl; Line l1( 0,0, 10, 10);
cout << “Line 2: “ << endl; Line l2( p1, p2 );
}
In this case, we are using a very simple set of classes that implement a point and a line. Notice that in the Line class, there are two separate constructors. One takes four data values, indicating the starting and ending x and y coordinates (see 9). The second takes two point objects to define the same coordinates, as shown in 10. The difference in the two constructors is how the data is assigned to the internal member variables in the two cases. In the first case, the two points in the Line class are initialized within the initialization line of the constructor code. In the second case, the two points are initialized by assignment within the constructor body. As we will see when the program runs, these two choices have very different results.
3. Save the source file in the code editor and then 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.
}
Point& BottomRight()
{
Choosing Initialization Instead of Assignment |
415 |
If you have done everything correctly, you should see the following output on the console window:
$ ./a.exe |
|
|
Point: full constructor called |
|
|
Point: full constructor called |
|
11 |
Line 1: |
|
|
Point: full constructor called |
|
|
Point: full constructor called |
|
12 |
Line 2: |
|
|
Point: void constructor called |
|
Point: void constructor called
Point: operator= called
Point: operator= called
Notice that in the first case (shown at |
|
11), we con- |
|
struct the two points as a part of |
constructing the |
||
|
|
|
line. This is as expected because the Line object contains two point objects. However, those two objects are constructed using the full constructor for the Point class, using the data values we passed in. This means there is no additional overhead for creating the points. In the second case, shown at
12, however, we not only have the two Pointobjects being created, but also the overhead of two assignment statements. This means that twice as much work is being done. If you initialize things using the initialization process in C++ constructors, you avoid the overhead of the assignments.
66 Documenting the
Data Flow
Technique
Save Time By
Learning how the code operates
Improving the readability of your code by documenting the data flow
Adding an undo system
Testing your code
Beginning programmers are often afraid to make adjustments to existing code for fear of destroying the code’s functionality. Existing code is simply part of life in the programming world, and
you can’t be afraid to just dig in and make changes to the code base. Other than to offer simple encouragement, I can’t really give you any advice on how to work with existing code; however, I can give you ideas on how to make your code easier to work with.
If you want to save a lot of time for yourself and all of the programmers who come after you, document the flow of data through the system. Most programmers document how the code works, or how you interface to the objects in the system. This is a nice thing, but the problems that crop up in coding are normally related to data, not code. In this technique, we are going to explore the most important part of the programming system, the data flow.
Learning How Code Operates
If you really want to know how the code in a system operates, just watch how it manipulates data. The surest way to do so is to keep track of all changes in a system. Although we normally think of an object-oriented system as having member variables and methods to access those variables, there is really no reason to do things this way. We can simply implement a system that stores the data in properties and then accesses those properties through standard methods of the property manipulation class rather than through the parent class. The following steps show you this exact process, implementing a property holding class and providing methods to track the changes to the data as it goes through the system.
Learning How Code Operates 417
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 ch66.cpp, although you can use whatever you choose.
2. Type the code from Listing 66-1 into your file.
Better yet, copy the code from the source file on this book’s companion Web site.
LISTING 66-1: IMPLEMENTING PROPERTIES AS A CLASS
#include <map> #include <string> #include <iostream> #include <stack> #include <stdlib.h>
using namespace std;
class State
{
string _name; string _value;
public:
State(void)
{
_name = “”; _value = “”;
}
State( const char *name, const char *value )
{
setName( name ); setValue( value );
}
State( const State& aCopy )
{
setName( aCopy.getName() ); setValue( aCopy.getValue() );
}
void setName( const char *n )
{
_name = n;
}
void setName( const string& n )
{
_name = n;
}
string getName(void) const
{
return _name;
}
void setValue( const char *v )
{
_value = v;
}
void setValue( const string& v )
{
_value = v;
}
string getValue( void ) const
{
return _value;
}
}; |
|
|
|
class Properties |
|
|
1 |
{ |
|
|
|
map<string, string> _props; |
2 |
||
stack<State> |
_previous; |
public:
Properties(void)
{
}
Properties( const Properties& aCopy )
{
map<string, string>::iterator iter; for ( iter = _props.begin(); iter !=
_props.end(); ++iter ) _props[ (*iter).first ] =
(*iter).second;
}
virtual ~Properties()
{
}
void setProperty( const char *name, int value )
{
// First, see if its there if ( _props.find(name) !=
_props.end() )
{
State sold(name, _props[name]. c_str() ); (continued) 3