- •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
118 Technique 22: Using Virtual Inheritance
Implementing Virtual Inheritance
Implementing virtual inheritance in your base classes allows you to create an inheritance structure that will permit all other classes that inherit from your base classes to work properly. The following steps take a look at how we can create an inheritance structure that implements virtual inheritance:
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 ch22.cpp, although you can use whatever you choose.
2. Type the code from Listing 22-3 into your file.
Or better yet, copy the code from the source file on this book’s companion Web site.
LISTING 22-3: BASE-CLASS INHERITANCE
#include <stdio.h> #include <string>
class Object
{
private:
char *name; public:
Object(void)
{
name=NULL;
}
Object(const char *n)
{
setName( n );
}
virtual ~Object()
{
if ( name ) delete name; name = NULL;
}
virtual const char *Name()
{
return name;
}
virtual void setName(const char *n)
{
if ( name ) delete name;
name = NULL;
if ( n )
{
name = new char[strlen(n)+1]; strcpy( name, n );
}
}
}; |
|
|
class A : public virtual Object |
|
1 |
{ |
|
|
public: |
|
|
A() |
|
|
: Object(“A”) |
|
|
{ |
|
|
} |
|
|
virtual ~A() |
|
|
{ |
|
|
} |
|
|
}; |
|
|
class B : public virtual Object |
|
2 |
{ |
|
public:
B()
: Object(“B”)
{
}
};
class C : public A, public B
{
public:
C()
{
}
void Display()
{
printf(“Name = %s\n”, Name() );
}
};
int main(int argc, char **argv)
{
C c;
c.Display();
}
Correcting the Code 119
The keys to the above code are in the lines marked with 1 and 2. These two lines force the compiler to create only a single Object instance in the inheritance tree of both A and B.
3. Save the code as a file in your code editor and close the editor application.
4. Compile the source-code file with your favorite compiler on your favorite operating system, and then run the resulting executable.
If you have done everything right, you should see the following output:
$ ./a.exe Name = (null)
Oops. This is not what we wanted to see. We were expecting the name of the class. That name should be ‘C’. The next section fixes that — and gives us the type we really wanted.
Correcting the Code
The problem in our simple example comes about because we assumed that the code would naturally follow one of the two paths through the inheritance tree and assign a name to the class. With virtual inheritance, no such thing happens. The compiler has no idea which class we want to assign the name
to, since the values “belong” to the C class, rather than the A and B classes. We have to tell the code what to do. Let’s do that here.
1. Reopen the source-code file created earlier
(called ch22.cpp) and edit it in your code editor.
2. Modify the constructor for the C class as follows:
C()
: Object(“C”)
{
}
3. Recompile and run the program, and you will see the following (correct) output from the application:
$ ./a.exe Name = C
It isn’t always possible to modify the base classes for a given object, but when you can, use this technique to avoid the “dread diamond” (having a class derived from two base classes both of which derive from
a common base class) — and use classes that have common bases as your own base classes.
When you’re designing a class, keep in mind that if you add a virtual method, you should always inherit from the class virtually. This way, all derived classes will be able to override the functionality of that virtual method directly.
23 Creating Overloaded
Operators
Technique
Save Time By
Defining overloaded operators
Rules for creating overloaded operators
Using a conversion operator
Using overloaded operators
Testing your operator
One of the most fascinating abilities that was added to C++ was the power to actually change the way the compiler interpreted the language. Before C++, if you had a class called, say, Foo, and you
wanted to write a method or function to add two Foo objects, you would have to write code similar to the following:
Foo addTwoFoos( const Foo&f1, const Foo& f2)
{
Foo f3;
// Do something to add the two foos (f1 and f2)
return f3;
}
Then you could call the function in your application code like this:
Foo f1(0);
Foo f2(1); Foo f3;
f3 = addTwoFoos(f1,f2);
Overloaded operators permit you to change the basic syntax of the language, such as changing the way in which the plus operator (+) is used. With the addition of overloaded operators, however, you can now write something like this:
Foo |
operator+(const |
Foo& f1, |
{ |
const Foo& f2 ) |
1 |
|
Foo f3; |
|
|
// Do something |
to add them |
} |
return f3; |
|
|
|
Rules for Creating Overloaded Operators |
121 |
In your code, you can now include statements such as this:
Foo f3 = f1+f2; |
|
|
Without the overloaded operator ( |
|
1), this line |
since the compiler |
||
would generate a compile error, |
|
|
knows of no way to add two objects of type Foo.
Of course, this power comes with a corresponding price. When you overload operators like this, even the simplest-looking statement can cause problems. Because you can no longer assume that a single line of code results in a single operation, you must step into every line of code in the debugger to trace through and see what is really happening.
Take a look at this simple-looking statement, for example, in which we assign one Foo object to another:
Foo f1=12;
This statement could, conceivably, be hidden within hundreds of lines of code. If an error crops up in the code that processes this simple assignment statement, you have to dig into every one of those lines to find it. So consider: An overloaded operator may be hard to beat for readability. It is more intuitive to say A+B when you mean to add two things than to write add(A,B), but it’s a debugging nightmare. Weigh very carefully the real need for overloading a particular operator against the pain you can cause someone who’s trying to figure out why a side effect in your code caused his program not to work.
Rules for Creating Overloaded Operators
There are four basic rules that you should use when you overload operators in your own classes:
Make sure that the operator does not conflict with standard usage of that operator.
This rule is pretty straightforward. If you overload the plus (+) operator, you still expect the
operator to do something along the line of adding. You wouldn’t expect (for example) to use the plus operator to invert a string; that wouldn’t make sense. It would drive anyone trying to understand the code completely batty.
Make sure that the operator has no unexpected side effects.
This rule isn’t much more complicated. If I’m adding two numbers together, I don’t expect the result of the operation to change either number. For example, suppose we wrote something like
Foo f1(1); Foo f2(2);
Foo f3 = f1+f2;
After these statements are run, you certainly would expect f1 to still contain a 1 and f2 to still contain a 2. It would be confusing and counterintuitive if you added two numbers and found that f1 was now 3 and f2 was now 5.
Make sure that an operator is the only way you can implement the functionality without having an adverse impact on the end user and the maintainer of the code.
This rule is somewhat subjective but easy to understand. If you could easily write an algorithm as a method or function, there would be no reason to overload an operator to perform the algorithm.
Make sure that the operator and all associated operators are implemented.
This rule is fairly important — especially from the perspective of a user. For example, if you implement the plus (+) operator, you’re going to want to implement the plus-equal operator (+=) as well. It makes no sense to the end user to be able to perform the statement
Foo f3 = f1 + f2;
without also being able to perform this one: f2 += f1;
Unfortunately, the converse operation is not always valid. For example, we might be able to add two strings together easily enough, by