- •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 331
Justifying a New Error Mechanism?
What’s wrong with error returns like FORTRAN used to make? Factorials cannot be negative, so I could have said something like “Okay, if factorial() detects an error, it returns a negative number. The actual value indicates the source of the problem.” What’s wrong with that? That’s how it’s been accomplished for ages.
Unfortunately, several problems arise. First, although it’s true that the result of a factorial can’t be negative, other functions aren’t so lucky. For example, you can’t take the log of a negative number either, but the negative return value trick won’t work here — logarithms can be either negative or positive.
Second, there’s just so much information that you can store in an integer. Maybe you can have –1 for “argument is negative” and –2 for “argument is too large.” But, if the argument is too large, you want to know what the argument is, because that information might help you debug the problem. There’s no place to store that type of information.
Third, the processing of error returns is optional. Suppose someone writes factorial() so that it dutifully checks the argument and returns a negative number if the argument is out of range. If a function that calls factorial() doesn’t check the error return, returning an error value doesn’t do any good. Sure, you can make all kinds of menacing threats, such as “You will check your error returns or else,” and the programmer may have the best of inten tions, but you all know that people get lazy and return to their old, non-error- checking ways.
Even if you do check the error return from factorial() or any other func tion, what can the function do with the error? It can probably do nothing more than output an error message of your own and return another error indication to the caller, which probably does the same. Pretty soon, all code begins to have the following appearance:
//call some function, check the error return, handle it,
//and return
errRtn = someFunc(); if (errRtn)
{
errorOut(“Error on call to someFunc()”); return MY_ERROR_1;
}
errRtn = someOtherFunc(); if (errRtn)
{
errorOut(“Error on call to someOtherFunc()”); return MY_ERROR_1;
}
332 Part V: Optional Features
This mechanism has several problems:
It’s highly repetitive.
It forces the user to invent and keep track of numerous error return indications.
It mixes the error-handling code into the normal code flow, thereby obscuring the normal, non-error path.
These problems don’t seem so bad in this simple example, but they become increasingly worse as the calling code becomes more complex. The result is that error-handling code doesn’t get written to handle all the conditions that it should.
The exception mechanism addresses these problems by removing the error path from the normal code path. Furthermore, exceptions make error han dling obligatory. If your function doesn’t handle the thrown exception, control passes up the chain of called functions until C++ finds a function to handle the error. This also gives you the flexibility to ignore errors that you can’t do any thing about anyway. Only the functions that can actually correct the problem need to catch the exception.
Examining the Exception Mechanism
Take a closer look at the steps that the code goes through to handle an excep tion. When the throw occurs, C++ first copies the thrown object to some neu tral place. It then begins looking for the end of the current try block.
If a try block is not found in the current function, control passes to the calling function. A search is then made of that function. If no try block is found there, control passes to the function that called it, and so on up the stack of calling functions. This process is called unwinding the stack.
An important feature of stack unwinding is that as each stack is unwound, objects that go out of scope are destructed just as though the function had executed a return statement. This keeps the program from losing assets or leaving objects dangling.
When the encasing try block is found, the code searches the first catch phrase immediately following the closing brace of the catch block. If the object thrown matches the type of argument specified in the catch statement, control passes to that catch phrase. If not, a check is made of the next catch phrase. If no matching catch phrases are found, the code searches for the next higher level try block in an ever-outward spiral until an appropriate catch can be found. If no catch phrase is found, the program is terminated.
Chapter 25: Handling Errors — Exceptions 333
Consider the following example:
// CascadingException - note that the following program |
|
// |
may generate warnings because the |
// |
variables f, i and pMsg |
// |
are not used for anything - the |
// |
compiler is trying to give you a |
// |
hint that maybe you don’t |
// |
need the arguments at all |
#include <cstdio> |
|
#include <cstdlib> |
|
#include <iostream> |
|
using namespace std; |
|
class Obj |
|
{ |
|
public: |
|
Obj(char c) |
|
{ |
|
label = c;
cout << “Constructing object “ << label << endl;
}
~Obj()
{
cout << “Destructing object “ << label << endl;
}
protected: char label;
};
void f1(); void f2(); int f3()
{
Obj a(‘a’); try
{
Obj b(‘b’); f1();
}
catch(float f)
{
cout << “Float catch” << endl;
}
catch(int i)
{
cout << “Int catch” << endl;
}
catch(...)
{
cout << string(“Generic catch”) << endl;
}
}
334 Part V: Optional Features
int main(int nNumberofArgs, char* pszArgs[])
{
f3();
//wait until user is ready before terminating program
//to allow the user to see the program results system(“PAUSE”);
return 0;
}
void f1()
{
try
{
Obj c(‘c’); f2();
}
catch(string msg)
{
cout << “String catch” << endl;
}
}
void f2()
{
Obj d(‘d’); throw 10;
}
The output from executing this program appears as follows:
Constructing object a
Constructing object b
Constructing object c
Constructing object d
Destructing object d
Destructing object c
Destructing object b
Int catch
Destructing object a
Press any key to continue . . .
First, you see the four objects a, b, c, and d being constructed as control passes through each declaration before f2() throws the int 10. Because no try block is defined in f2(), C++ unwinds f2()’s stack, causing object d to be destructed. f1() defines a try block, but its only catch phrase is designed to handle char*, which doesn’t match the int thrown. Therefore, C++ continues looking. This unwinds f1()’s stack, resulting in object c being destructed.
Back in f3(), C++ finds another try block. Exiting that block causes object b to go out of scope. The first catch phrase is designed to catch floats that don’t