- •Table of Contents
- •Introduction
- •What Is C++?
- •Conventions Used in This Book
- •How This Book Is Organized
- •Part I: Introduction to C++ Programming
- •Part III: Introduction to Classes
- •Part IV: Inheritance
- •Part V: Optional Features
- •Part VI: The Part of Tens
- •Icons Used in This Book
- •Where to Go from Here
- •Grasping C++ Concepts
- •How do I program?
- •Installing Dev-C++
- •Setting the options
- •Creating Your First C++ Program
- •Entering the C++ code
- •Building your program
- •Executing Your Program
- •Dev-C++ is not Windows
- •Dev-C++ help
- •Reviewing the Annotated Program
- •Examining the framework for all C++ programs
- •Clarifying source code with comments
- •Basing programs on C++ statements
- •Writing declarations
- •Generating output
- •Calculating Expressions
- •Storing the results of expression
- •Declaring Variables
- •Declaring Different Types of Variables
- •Reviewing the limitations of integers in C++
- •Solving the truncation problem
- •Looking at the limits of floating-point numbers
- •Declaring Variable Types
- •Types of constants
- •Special characters
- •Are These Calculations Really Logical?
- •Mixed Mode Expressions
- •Performing Simple Binary Arithmetic
- •Decomposing Expressions
- •Determining the Order of Operations
- •Performing Unary Operations
- •Using Assignment Operators
- •Why Mess with Logical Operations?
- •Using the Simple Logical Operators
- •Storing logical values
- •Using logical int variables
- •Be careful performing logical operations on floating-point variables
- •Expressing Binary Numbers
- •The decimal number system
- •Other number systems
- •The binary number system
- •Performing Bitwise Logical Operations
- •The single bit operators
- •Using the bitwise operators
- •A simple test
- •Do something logical with logical calculations
- •Controlling Program Flow with the Branch Commands
- •Executing Loops in a Program
- •Looping while a condition is true
- •Using the for loop
- •Avoiding the dreaded infinite loop
- •Applying special loop controls
- •Nesting Control Commands
- •Switching to a Different Subject?
- •Writing and Using a Function
- •Divide and conquer
- •Understanding the Details of Functions
- •Understanding simple functions
- •Understanding functions with arguments
- •Overloading Function Names
- •Defining Function Prototypes
- •Variable Storage Types
- •Including Include Files
- •Considering the Need for Arrays
- •Using an array
- •Initializing an array
- •Accessing too far into an array
- •Using arrays
- •Defining and using arrays of arrays
- •Using Arrays of Characters
- •Creating an array of characters
- •Creating a string of characters
- •Manipulating Strings with Character
- •String-ing Along Variables
- •Variable Size
- •Address Operators
- •Using Pointer Variables
- •Comparing pointers and houses
- •Using different types of pointers
- •Passing Pointers to Functions
- •Passing by value
- •Passing pointer values
- •Passing by reference
- •Limiting scope
- •Examining the scope problem
- •Providing a solution using the heap
- •Defining Operations on Pointer Variables
- •Re-examining arrays in light of pointer variables
- •Applying operators to the address of an array
- •Expanding pointer operations to a string
- •Justifying pointer-based string manipulation
- •Applying operators to pointer types other than char
- •Contrasting a pointer with an array
- •Declaring and Using Arrays of Pointers
- •Utilizing arrays of character strings
- •Identifying Types of Errors
- •Choosing the WRITE Technique for the Problem
- •Catching bug #1
- •Catching bug #2
- •Calling for the Debugger
- •Defining the debugger
- •Finding commonalities among us
- •Running a test program
- •Single-stepping through a program
- •Abstracting Microwave Ovens
- •Preparing functional nachos
- •Preparing object-oriented nachos
- •Classifying Microwave Ovens
- •Why Classify?
- •Introducing the Class
- •The Format of a Class
- •Accessing the Members of a Class
- •Activating Our Objects
- •Simulating real-world objects
- •Why bother with member functions?
- •Adding a Member Function
- •Creating a member function
- •Naming class members
- •Calling a Member Function
- •Accessing a member function
- •Accessing other members from a member function
- •Defining a Member Function in the Class
- •Keeping a Member Function After Class
- •Overloading Member Functions
- •Defining Arrays of and Pointers to Simple Things
- •Declaring Arrays of Objects
- •Declaring Pointers to Objects
- •Dereferencing an object pointer
- •Pointing toward arrow pointers
- •Passing Objects to Functions
- •Calling a function with an object value
- •Calling a function with an object pointer
- •Calling a function by using the reference operator
- •Returning to the Heap
- •Comparing Pointers to References
- •Linking Up with Linked Lists
- •Performing other operations on a linked list
- •Hooking up with a LinkedListData program
- •A Ray of Hope: A List of Containers Linked to the C++ Library
- •Protecting Members
- •Why you need protected members
- •Discovering how protected members work
- •Protecting the internal state of the class
- •Using a class with a limited interface
- •Creating Objects
- •Using Constructors
- •Why you need constructors
- •Making constructors work
- •Dissecting a Destructor
- •Why you need the destructor
- •Working with destructors
- •Outfitting Constructors with Arguments
- •Justifying constructors
- •Using a constructor
- •Defaulting Default Constructors
- •Constructing Class Members
- •Constructing a complex data member
- •Constructing a constant data member
- •Constructing the Order of Construction
- •Local objects construct in order
- •Static objects construct only once
- •Global objects construct in no particular order
- •Members construct in the order in which they are declared
- •Destructors destruct in the reverse order of the constructors
- •Copying an Object
- •Why you need the copy constructor
- •Using the copy constructor
- •The Automatic Copy Constructor
- •Creating Shallow Copies versus Deep Copies
- •Avoiding temporaries, permanently
- •Defining a Static Member
- •Why you need static members
- •Using static members
- •Referencing static data members
- •Uses for static data members
- •Declaring Static Member Functions
- •What Is This About, Anyway?
- •Do I Need My Inheritance?
- •How Does a Class Inherit?
- •Using a subclass
- •Constructing a subclass
- •Destructing a subclass
- •Having a HAS_A Relationship
- •Why You Need Polymorphism
- •How Polymorphism Works
- •When Is a Virtual Function Not?
- •Considering Virtual Considerations
- •Factoring
- •Implementing Abstract Classes
- •Describing the abstract class concept
- •Making an honest class out of an abstract class
- •Passing abstract classes
- •Factoring C++ Source Code
- •Defining a namespace
- •Implementing Student
- •Implementing an application
- •Project file
- •Creating a project file under Dev-C++
- •Comparing Operators with Functions
- •Inserting a New Operator
- •Overloading the Assignment Operator
- •Protecting the Escape Hatch
- •How Stream I/O Works
- •The fstream Subclasses
- •Reading Directly from a Stream
- •Using the strstream Subclasses
- •Manipulating Manipulators
- •Justifying a New Error Mechanism?
- •Examining the Exception Mechanism
- •What Kinds of Things Can I Throw?
- •Adding Virtual Inheritance
- •Voicing a Contrary Opinion
- •Generalizing a Function into a Template
- •Template Classes
- •Do I Really Need Template Classes?
- •Tips for Using Templates
- •The string Container
- •The list Containers
- •Iterators
- •Using Maps
- •Enabling All Warnings and Error Messages
- •Insisting on Clean Compiles
- •Limiting the Visibility
- •Avoid Overloading Operators
- •Heap Handling
- •Using Exceptions to Handle Errors
- •Avoiding Multiple Inheritance
- •Customize Editor Settings to Your Taste
- •Highlight Matching Braces/Parentheses
- •Enable Exception Handling
- •Include Debugging Information (Sometimes)
- •Create a Project File
- •Customize the Help Menu
- •Reset Breakpoints after Editing the File
- •Avoid Illegal Filenames
- •Include #include Files in Your Project
- •Executing the Profiler
- •System Requirements
- •Using the CD with Microsoft Windows
- •Using the CD with Linux
- •Development tools
- •Program source code
- •Index
Chapter 25: Handling Errors — Exceptions 335
match the int, so it’s skipped. The next catch phrase matches the int exactly, so control stops there. The final catch phrase, which would catch any object thrown, is skipped because a matching catch phrase was already found.
What Kinds of Things Can I Throw?
The thing following the throw keyword is actually an expression that creates an object of some kind. In the examples so far, I’ve thrown an int and a string object, but throw can handle any type of object. This means that you can throw almost as much information as you want. Consider the following update to the factorial program, CustomExceptionClass:
//
//CustomExceptionClass - demonstrate exceptions using
// |
a factorial function |
// |
|
#include <cstdio> |
|
#include <cstdlib> |
|
#include <iostream> |
|
#include <sstream> |
|
using namespace std; |
|
// Exception - generic exception handling class class Exception
{
public:
Exception(char* pMsg, int n, char* pFile, int nLine)
: msg(pMsg), errorValue(n), file(pFile), lineNum(nLine) {}
virtual string display()
{
ostringstream out;
out << “Error <” << msg
<<“ - value is “ << errorValue
<<“>\n”;
out << “ @” << file << “-” << lineNum << endl; return out.str();
}
protected:
// error message string msg;
int errorValue;
// file name and line number where error occurred string file;
int lineNum;
};
// factorial - compute factorial
336 Part V: Optional Features
int factorial(int n)
{
//you can’t handle negative values of n;
//better check for that condition first if (n < 0)
{
throw Exception(“Argument for factorial negative”, n, __FILE__, __LINE__);
}
// go ahead and calculate factorial int accum = 1;
while(n > 0)
{
accum *= n; n--;
}
return accum;
}
int main(int nNumberofArgs, char* pszArgs[])
{
try
{
// this will work
cout << “Factorial of 3 is “ << factorial(3) << endl;
// this will generate an exception
cout << “Factorial of -1 is “ << factorial(-1) << endl;
}
//control passes here catch(Exception e)
{
cout << “Error occurred: \n” << e.display() << endl;
}
//wait until user is ready before terminating program
//to allow the user to see the program results system(“PAUSE”);
return 0;
}
This program appears much the same as the factorial program at the beginning of this chapter. The difference is the use of a user-defined Exception class that contains more information concerning the nature of the error than a simple string contains. The factorial program is able to throw the error message, the illegal value, and the exact location where the error occurred.
__FILE__ and __LINE__ are intrinsic #defines that are set to the name of the source file and the current line number in that file, respectively.
Chapter 25: Handling Errors — Exceptions 337
The catch snags the Exception object and then uses the built-in display() member function to display the error message. The output from this program appears as follows:
Factorial of 3 is 6
Error occurred:
Error <Argument for factorial negative - value is -1>
@//cpp_programs/Chap25/CustomExceptionClass.cpp-46
Press any key to continue . . .
The Exception class represents a generic error-reporting class. However, you can inherit from this class to provide further detail for a particular type of error. For example, I can define an InvalidArgumentException class that stores the value of the invalid argument in addition to the message and loca tion of the error:
class InvalidArgumentException : public Exception
{
public: InvalidArgumentException(int arg,
char* pFile, int nLine)
: Exception(“Invalid argument”, pFile, nLine)
{
invArg = arg;
}
virtual void display(ostream& out)
{
Exception::display(out);
out << “Argument was “ << invArg << endl;
}
protected: int invArg;
};
The calling function automatically handles the new InvalidArgument Exception because an InvalidArgumentException is an Exception and the display() member function is polymorphic.
338 Part V: Optional Features
Chapter 26
Inheriting Multiple Inheritance
In This Chapter
Introducing multiple inheritance
Avoiding ambiguities with multiple inheritance
Avoiding ambiguities with virtual inheritance
Figuring out the ordering rules for multiple constructors
Getting a handle on problems with multiple inheritance
In the class hierarchies discussed in other chapters, each class has inher ited from a single parent. Such single inheritance is sufficient to describe
most real-world relationships. Some classes, however, represent the blending of two classes into one. (Sounds sort of romantic, doesn’t it.)
An example of such a class is the sleeper sofa. As the name implies, it is a sofa and a bed (although not a very comfortable bed). Thus, the sleeper sofa should be allowed to inherit bed-like properties. To address this situation, C++ allows a derived class to inherit from more than one base class. This is called multiple inheritance.
Describing the Multiple
Inheritance Mechanism
To see how multiple inheritance works, look at the sleeper sofa example. Figure 26-1 shows the inheritance graph for class SleeperSofa. Notice how this class inherits from class Sofa and from class Bed. In this way, it inherits the properties of both.
340 Part V: Optional Features
|
Bed |
|
Sofa |
sleep( ) |
weight |
watchTV( ) |
weight |
Figure 26-1: |
|
SleeperSofa |
|
Class |
|
|
|
|
|
|
|
hierarchy of |
foldOut( ) |
|
|
a sleeper |
|
|
|
sofa. |
|
|
|
The code to implement class SleeperSofa looks like the following:
//
//MultipleInheritance - a single class can inherit from
// |
more than one base class |
// |
|
#include <cstdio> |
|
#include <cstdlib> |
|
#include <iostream> |
|
using namespace std; |
|
class Bed
{
public:
Bed(){}
void sleep(){ cout << “Sleep” << endl; } int weight;
};
class Sofa
{
public:
Sofa(){}
void watchTV(){ cout << “Watch TV” << endl; } int weight;
};
// SleeperSofa - is both a Bed and a Sofa class SleeperSofa : public Bed, public Sofa
{
public:
SleeperSofa(){}
void foldOut(){ cout << “Fold out” << endl; }
};
Chapter 26: Inheriting Multiple Inheritance 341
int main(int nNumberofArgs, char* pszArgs[])
{
SleeperSofa ss;
// you can watch TV on a sleeper sofa like a sofa...
ss.watchTV(); |
// Sofa::watchTV() |
//...and then you can fold it out... |
|
ss.foldOut(); |
// SleeperSofa::foldOut() |
//...and sleep on it ss.sleep();
//wait until user is ready before terminating program
//to allow the user to see the program results system(“PAUSE”);
return 0;
}
Here the class SleeperSofa inherits from both Bed and Sofa. This is apparent from the appearance of both classes in the class declaration. SleeperSofa inherits all the members of both base classes. Thus, both of the calls ss.sleep() and ss.watchTV() are legal. You can use a SleeperSofa as a Bed or a Sofa. Plus the class SleeperSofa can have members of its own, such as foldOut(). The output of this program appears as follows:
Watch TV
Fold out
Sleep
Press any key to continue . . .
Is this a great country or what?
Straightening Out Inheritance
Ambiguities
Although multiple inheritance is a powerful feature, it introduces several possi ble problems. One is apparent in the preceding example. Notice that both Bed and Sofa contain a member weight. This is logical because both have a mea surable weight. The question is, “Which weight does SleeperSofa inherit?”
The answer is “both.” SleeperSofa inherits a member Bed::weight and a separate member Sofa::weight. Because they have the same name, unqual ified references to weight are now ambiguous. This is demonstrated in the following snippet: