- •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
182 Technique 31: Extending a Template Class
LISTING 31-1 (continued)
{
printf(“TemplateAsMember\n”); _fooEntry.Print();
}
};
//Case 2: Using the base template as a base class
class TemplateAsBase : public Base<Foo>
{
public:
TemplateAsBase(void)
: Base<Foo>( “TemplateAsBase”, NULL )
{
}
TemplateAsBase(const char *name, Foo *pFoo)
: Base<Foo>( name, pFoo )
{
}
virtual ~TemplateAsBase(void)
{
}
void Print()
{
printf(“TemplateAsBase:\n”);
Base<Foo>::Print();
}
};
//Case 3: Using the base template as a base class
//for another templated class
template < class A, class B >
class TemplateAsBaseTemplate : public Base<A>
{
private:
B *_anotherPointer; public:
TemplateAsBaseTemplate( void )
:Base<Foo>( “TemplateAsBaseTemplate”, NULL )
{
_anotherPointer = NULL;
}
TemplateAsBaseTemplate( A* anA, B* aB )
:Base<Foo>( “TemplateAsBaseTemplate”, anA )
{
_anotherPointer = aB;
}
B* getBPointer()
{
return _anotherPointer;
}
void Print()
{
Base<A>::Print();
if ( _anotherPointer ) _anotherPointer->Print();
else
printf(“Another pointer is NULL\n”);
}
};
class AnotherBase
{
private: int x;
public:
AnotherBase()
{
x = 0;
}
AnotherBase( int i )
{
x = i;
}
virtual ~AnotherBase(void)
{
}
void Print()
{
printf(“AnotherBase: x = %d\n”, x );
}
};
In Listing 31-1, the code shows how each possible case is addressed and used. We have implemented two “normal” classes, called Foo and AnotherBase, which are used as template arguments to designate the template classes.
Testing the Template Classes
To check whether the code is really working, we need to implement a test driver. The following steps do so for the code in Listing 31-1:
Testing the Template Classes 183
1. In the code editor of your choice, reopen the source file for the code you just created.
In this example, the file is named ch31.cpp, although you can use whatever you choose.
2. Append the code from Listing 31-2 to your file.
Better yet, copy the code from the source file on this book’s companion Web site.
LISTING 31-2: THE TEST DRIVER FOR THE TEMPLATED CLASS
EXAMPLE
int main()
{
printf(“Creating base\n”); Base<Foo> fooBase;
printf(“Creating template as member\n”); TemplateAsMember tempMem; printf(“Creating template as base\n”); TemplateAsBase tempBase; printf(“Creating template as base template\n”); TemplateAsBaseTemplate<Foo,AnotherBase> tempAsBT;
fooBase.Print();
tempMem.Print();
tempBase.Print();
tempAsBT.Print();
return 0;
}
3. Save the source file in your code editor and close the code editor.
4. Compile the source code with the compiler of your choice, on the operating system of your choice.
When the program is run, if you have done everything properly, you should see the following output in the shell window:
Creating base
Void constructor called Creating template as member Creating base with name [TemplateAsMember] Creating template as base
Creating base with name [TemplateAsBase]
Creating template as base template |
|
|
Creating base with name |
|
|
[TemplateAsBaseTemplate] |
|
|
Base: |
|
|
Name = Nothing |
|
|
Pointer = |
|
1 |
Pointer is NULL |
|
|
TemplateAsMember |
|
Base:
Name = TemplateAsMember
Pointer =
Pointer is NULL
TemplateAsBase:
Base:
Name = TemplateAsBase
Pointer =
Pointer is NULL
Base:
Name = TemplateAsBaseTemplate
Pointer =
Pointer is NULL
Another pointer is NULL
The output from this program shows us that each of the various template instantiations works. As you can see, in each case (see, for example, the line marked 1), the constructor was called and the various member variables assigned proper default values. Looking at the examples, it should be clear that each of the various methods arrives at the same conclusion.
Concrete classes that have been made into templates as a specific form of a class are best suited for extension. This is to say, if you have a template class that accepts a particular type of class for its argument, you are better off extending your template class by creating a form of it as a specific class — and then deriving from that specific class. The reason for this is more human than technical: People usually don’t think in terms of templates so much as in terms of class names.
184 Technique 31: Extending a Template Class
Using Non-class Template
Arguments
Looking at the four choices in extending base classes, you will probably notice a few things that suggest particular approaches to the process.
To utilize methods in a base class being used as a template, you must specify which version of the class the template is to use. The reason is that you could conceivably have a class that inherited from multiple classes of the same template, with different types associated with it. This is a good thing because it allows you to segregate specific functionality into separate classes.
Another thing worth noticing is that you may create a templated class that does not require a class as its argument. For example, we could create a template with a numeric argument; the following steps show you how:
1. In the code editor of your choice, create a new file.
In this example, the file is named ch31a.cpp, although you can use whatever you choose.
2. Type the code from Listing 31-3 into your file.
LISTING 31-3: A TEMPLATE CLASS WITH A NON-CLASS
ARGUMENT
template <class A> class LessThanTen
{
A _element;
public:
LessThanTen( A entry )
{
set ( entry );
}
LessThanTen( const LessThanTen& aCopy )
{ |
|
|
|
set( |
aCopy._element); |
|
|
} |
|
|
3 |
void set( |
A value ) |
|
|
{ |
|
|
if ( value > 0 && value < 10 ) |
|
2 |
_element = value; |
|
}
A get()
{
return _element;
}
};
This code works for a class argument, so long as that class can be compared to an integer. It can also be used for a non-class argument, such as an integer, long integer, or even a floating point value.
With this class shown in Listing 31-3, there is no reason that the template argument should be a class. In fact, it would be implied that a numeric element was used, since the value which is passed into the set method is compared to an integral value of 10 (see the line marked 2).
3. Add the following code to your source file to test the integer class. This code will test the template class we just defined above.
int main(void) |
|
|
|
{ |
|
|
|
LessThanTen<int> ten(3); |
|
5 |
|
printf(“The |
value is %d\n”, |
|
|
ten.get() ); |
|
|
|
ten.set( 23 |
); |
|
4 |
printf(“The |
value is now %d\n”, |
|
ten.get() ); return 0;
}
4. Save the source file in your code editor and then close the code editor.
5. Compile the source code with the compiler of your choice, on the operating system of your choice.
When the program is run, if you have done everything properly, you should see the following output in the shell window:
Using Non-class Template Arguments |
185 |
$ ./a.exe
The value is 3 The value is now 3
As you can see from the output, the program indeed does properly create a new class that is a templated version of the LessThanTen class, using an integer as its template argument. The resulting class contains a method called set that takes an integer argument (shown at 3) that must be between 0 and 10. Since our value (see 4) is not within that range, it is not assigned, and we see that the print statement following the assignment still contains the value 3.
Notice that the code works with an integer argument (see the line marked with 5), even though the template specifies a class argument. For C++, integers, floating-point numbers, and the like can be considered first-class (that is, a basic type) arguments for the purpose of templates. In fact (in this case at least), any argument can be used, so long as the code includes comparison operators greater-than
(>) and less-than (<) to ensure that the set method works properly.
The ability to use either classes or basic types as template arguments makes the template construct extremely powerful in C++. Because you can write a single class that manages number, character strings, or class values and have it work seamlessly, you save enormous amounts of time and duplicated work.