The class methods will create objects of this class if they throw exceptions of the BadIndex type.. Listing 15.12 arraydbe.h // arraydbe.h -- define array class with exceptions #ifndef A
Trang 1hold the value of the bad index Note that the nested class declaration just describes the
class; it doesn't create objects The class methods will create objects of this class if they
throw exceptions of the BadIndex type Also note that the nested class is public This
allows the catch blocks to have access to the type Listing 15.12 shows the new header
file The rest of the definition, aside from changing the class name, is the same as the
definition of ArrayDb in Chapter 14 , except that it qualifies the method prototypes to
indicate which exceptions they can throw That is, it replaces
virtual double & operator[](int i);
with
virtual double & operator[](int i) throw(ArrayDbe::BadIndex &);
and so on (As with ordinary function arguments, it's usually better to pass references
instead of objects when throwing exceptions.)
Listing 15.12 arraydbe.h
// arraydbe.h define array class with exceptions
#ifndef ARRAYDBE_H_
#define ARRAYDBE_H_
#include <iostream>
using namespace std;
class ArrayDbE
{
private:
unsigned int size; // number of array elements
protected:
double * arr; // address of first element
public:
class BadIndex // exception class for indexing problems
{
private:
int badindex; // problematic index value
public:
Trang 2BadIndex(int i) : badindex(i) {}
virtual void Report() const;
};
ArrayDbE(); // default constructor
// create an ArrayDbE of n elements, set each to val
ArrayDbE(unsigned int n, double val = 0.0);
// create an ArrayDbE of n elements, initialize to array pn
ArrayDbE(const double * pn, unsigned int n);
// copy constructor
ArrayDbE(const ArrayDbE & a);
virtual ~ArrayDbE(); // destructor
unsigned int ArSize() const; // returns array size
double Average() const; // return array average
// overloaded operators
// array indexing, allowing assignment
virtual double & operator[](int i) throw(ArrayDbE::BadIndex &);
// array indexing (no =)
virtual const double & operator[](int i) const throw(ArrayDbE::BadIndex &);
ArrayDbE & operator=(const ArrayDbE & a);
friend ostream & operator<<(ostream & os, const ArrayDbE & a);
} ;
#endif
Next, you must provide the class methods These are the same methods used in Chapter
12 with the addition of some exception throwing Because the overloaded [] operators
throw exceptions instead of calling the exit() function, the program no longer needs to
include the cstdlib file Listing 15.13 shows the result.
Listing 15.13 arraydbe.cpp
// arraydbe.cpp ArrayDbE class methods
#include <iostream>
using namespace std;
#include "arraydbe.h"
Trang 3// BadIndex method
void ArrayDbE::BadIndex::Report() const
{
cerr << "Out of bounds index value: " << badindex << endl;
}
// ArrayDbE methods
// default constructor no arguments
ArrayDbE::ArrayDbE()
{
arr = NULL;
size = 0;
}
// constructs array of n elements, each set to val
ArrayDbE::ArrayDbE(unsigned int n, double val)
{
arr = new double[n];
size = n;
for (int i = 0; i < size; i++)
arr[i] = val;
}
// initialize ArrayDbE object to a non-class array
ArrayDbE::ArrayDbE(const double *pn, unsigned int n)
{
arr = new double[n];
size = n;
for (int i = 0; i < size; i++)
arr[i] = pn[i];
}
// initialize ArrayDbE object to another ArrayDbE object
ArrayDbE::ArrayDbE(const ArrayDbE & a)
{
size = a.size;
arr = new double[size];
Trang 4for (int i = 0; i < size; i++)
arr[i] = a.arr[i];
}
ArrayDbE::~ArrayDbE()
{
delete [] arr;
}
double ArrayDbE::Average() const
{
double sum = 0;
int i;
int lim = ArSize();
for (i = 0; i < lim; i++)
sum += arr[i];
if (i > 0)
return sum / i;
else
{
cerr << "No entries in score array\n";
return 0;
}
}
// return array size
unsigned int ArrayDbE::ArSize() const
{
return size;
}
// let user access elements by index (assignment allowed)
double & ArrayDbE::operator[](int i) throw(ArrayDbE::BadIndex &)
{
// check index before continuing
if (i < 0 || i >= size)
throw BadIndex(i);
return arr[i];
Trang 5// let user access elements by index (assignment disallowed)
const double & ArrayDbE::operator[](int i)const throw(ArrayDbE::BadIndex &)
{
// check index before continuing
if (i < 0 || i >= size)
throw BadIndex(i);
return arr[i];
}
// define class assignment
ArrayDbE & ArrayDbE::operator=(const ArrayDbE & a)
{
if (this == &a) // if object assigned to self,
return *this; // don't change anything
delete arr;
size = a.size;
arr = new double[size];
for (int i = 0; i < size; i++)
arr[i] = a.arr[i];
return *this;
}
// quick output, 5 values to a line
ostream & operator<<(ostream & os, const ArrayDbE & a)
{
int i;
for (i = 0; i < a.size; i++)
{
os << a.arr[i] << " ";
if (i % 5 == 4)
os << "\n";
}
if (i % 5 != 0)
os << "\n";
return os;
Trang 6Note that the exceptions now are objects instead of strings Also note that these exception
throws use the exception class constructor to create and initialize the exception objects:
if (i < 0 || i >= size)
throw BadIndex(i); // create, initialize a BadIndex object
What about catching this kind of exception? The exceptions are objects, not character
strings, so the catch block has to reflect that fact Also, because the exception is a nested
type, the code needs to use the scope resolution operator.
try {
}
catch(ArrayDbE::BadIndex &) {
}
Listing 15.14 provides a short program demonstrating the process.
Listing 15.14 exceptar.cpp
// exceptar.cpp use the ArrayDbE class
// Compile with arraydbe.cpp
#include <iostream>
using namespace std;
#include "arraydbe.h"
const int Players = 5;
int main()
{
try {
ArrayDbE Team(Players);
cout << "Enter free-throw percentages for your 5 "
"top players as a decimal fraction:\n";
int player;
Trang 7for (player = 0; player < Players; player++)
{
cout << "Player " << (player + 1) << ": % = ";
cin >> Team[player];
}
cout.precision(1);
cout.setf(ios_base::showpoint);
cout.setf(ios_base::fixed,ios_base::floatfield);
cout << "Recapitulating, here are the percentages:\n";
for (player = 0; player <= Players; player++)
cout << "Player #" << (player + 1) << ": "
<< 100.0 * Team[player] << "%\n";
} // end of try block
catch (ArrayDbE::BadIndex & bi) // start of handler
{
cout << "ArrayDbE exception:\n";
bi.Report();
} // end of handler
cout << "Bye!\n";
return 0;
}
Compatibility Note
Some compilers may not recognize the new form ios_base:: and will require ios:: instead.
Note the second for loop deliberately exceeds the array bounds, triggering an exception.
Here is a sample run:
Enter free-throw percentages for your 5 top players as a decimal fraction:
Player 1: % = 0.923
Player 2: % = 0.858
Player 3: % = 0.821
Player 4: % = 0.744
Trang 8Player 5: % = 0.697
Recapitulating, here are the percentages:
Player #1: 92.3%
Player #2: 85.8%
Player #3: 82.1%
Player #4: 74.4%
Player #5: 69.7%
ArrayDbE exception:
Out of bounds index value: 5
Bye!
Because the loop is inside the try block, throwing the exception terminates the loop as
control passes to the second catch block following the try block.
By the way, remember that variables defined in a block, including a try block, are local to
the block For example, the variable player is undefined once program control passes
beyond the try block in Listing 15.15
Exceptions and Inheritance
Inheritance interacts with exceptions in a couple of ways First, if a class has publicly
nested exception classes, a derived class inherits those exception classes Second, you
can derive new exception classes from existing ones We'll look at both these possibilities
in the next example.
First, derive a LimitArE class from the ArrayDbE class The LimitArE class will allow for
array indexing to begin with values other than 0 This can be accomplished by storing a
value representing the starting index and by redefining the functions Internally, the array
indexing still will begin with 0 But if, say, you specify 1900 as the starting index, the
operator[]() method will translate an external index of 1908 to an internal index
1908—1900, or 8 Listing 15.15 shows the details.
The BadIndex exception declared in the ArrayDbE class stored the offending index
value With variable index limits, it would be nice if the exception also stored the correct
range for indices You can accomplish this by deriving a new exception class from
BadIndex:
Trang 9class SonOfBad : public ArrayDbE::BadIndex
{
private:
int l_lim;
int u_lim;
public:
SonOfBad(int i, int l, int u) : BadIndex(i),
l_lim(l), u_lim(u) {}
void Report() const; // redefines Report()
} ;
You can nest the SonOfBad declaration in the LimitArE declaration Listing 15.15 shows
the result.
Listing 15.15 limarre.h
// limarre.h LimitArE class with exceptions
#ifndef LIMARRE_H_
#define LIMARRE_H_
#include "arraydbe.h"
class LimitArE : public ArrayDbE
{
public:
class SonOfBad : public ArrayDbE::BadIndex
{
private:
int l_lim;
int u_lim;
public:
SonOfBad(int i, int l, int u) : BadIndex(i),
l_lim(l), u_lim(u) {}
void Report() const;
} ;
Trang 10unsigned int low_bnd; // new data member
protected:
// handle bounds checking
virtual void ok(int i) const throw(ArrayDbE::BadIndex &);
public:
// constructors
LimitArE() : ArrayDbE(), low_bnd(0) {}
LimitArE(unsigned int n, double val = 0.0)
: ArrayDbE(n,val), low_bnd(0) {}
LimitArE(unsigned int n, int lb, double val = 0.0)
: ArrayDbE(n, val), low_bnd(lb) {}
LimitArE(const double * pn, unsigned int n)
: ArrayDbE(pn, n), low_bnd(0) {}
LimitArE(const ArrayDbE & a) : ArrayDbE(a), low_bnd(0) {}
// new methods
void new_lb(int lb) { low_bnd = lb;} // reset lower bound
int lbound() { return low_bnd;} // return lower bound
int ubound() { return ArSize() + low_bnd - 1;} // upper bound
// redefined operators
double & operator[](int i) throw(ArrayDbE::BadIndex &);
const double & operator[](int i) const throw(ArrayDbE::BadIndex &);
} ;
#endif
This design moves index checking from the overloaded [] methods to an ok() method that's
called by the overloaded [] methods C++ requires that the throw specification for virtual
method redefined in the derived class match the throw specification in the base class.
However, a base class reference can refer to a derived object Therefore, although the
LimitArE::ok() method throws a SonOfBad exception, the throw specifiers can list the
ArrayDbe::BadIndex class Listing 15.16 shows the class methods.
Listing 15.16 limarre.cpp
// limarre.cpp
#include "limarre.h"
Trang 11#include <iostream>
using namespace std;
// BadIndex method
void LimitArE::SonOfBad::Report() const
{
ArrayDbE::BadIndex::Report();
cerr << "Index should be in the range " << l_lim
<< " through " << u_lim << endl;
}
//limarre methods
// private method
// lower bound for array index is now low_bnd, and
// upper bound is now low_bnd + size - 1
void LimitArE::ok(int i) const throw(ArrayDbE::BadIndex &)
{
unsigned long size = ArSize();
if (i < low_bnd || i >= size + low_bnd)
throw SonOfBad(i, low_bnd, low_bnd + size - 1);
}
// redefined operators
double & LimitArE::operator[](int i) throw(ArrayDbE::BadIndex &)
{
ok(i);
return arr[i - low_bnd];
}
const double & LimitArE::operator[](int i) const throw(ArrayDbE::BadIndex &)
{
ok(i);
return arr[i - low_bnd];
}
}
Suppose you have a program with both ArrayDbE and LimitArE objects Then you would
Trang 12want a try block that catches the two possible exceptions: BadIndex and SonOfBad You
can do that by following the try block with two consecutive catch blocks:
try {
LimitArE income(Years, FirstYear);
ArrayDbE busywork(Years);
} // end of try block
catch (LimitArE::SonOfBad & bi) // 1st handler
{
}
catch (ArrayDbE::BadIndex & bi) // 2nd handler
{
}
When there is a sequence of catch blocks, a program attempts to match a thrown
exception to the first catch block, then the second catch block, and so on As soon as
there's a match, the program executes that catch block Providing the code in the catch
block doesn't terminate the program or generate another throw; the program jumps to the
statement following the final catch block after completing any one catch block in the
sequence.
This particular sequence of catch blocks has an interesting property—a catch block with a
BadIndex reference can catch either a BadIndex exception or a SonOfBad exception
That's because a base class reference can refer to a derived object However, a catch
block with a SonOfBad reference can't catch a BadIndex object That's because a
derived object reference can't refer to a base class object without an explicit type cast This
state of affairs suggests placing the SonOfBad catch block above the BadIndex catch
block That way, the SonOfBad catch block will catch a SonOfBad exception while
passing a BadIndex exception on to the next catch block The program in Listing 15.17
illustrates this approach.
Listing 15.17 excptinh.cpp
// excptinh.cpp use the ArrayDbE and LimitArE classes
Trang 13// Compile with arraydbe.cpp, limarre.cpp
#include <iostream>
using namespace std;
#include "arraydbe.h"
#include "limarre.h"
const int Years = 4;
const int FirstYear = 2001;
int main()
{
int year;
double total = 0;
try {
LimitArE income(Years, FirstYear);
ArrayDbE busywork(Years);
cout << "Enter your income for the last " << Years
<< " years:\n";
for (year = FirstYear; year < FirstYear + Years; year++)
{
cout << "Year " << year << ": $";
cin >> income[year];
busywork[year - FirstYear] = 0.2 * income[year];
}
cout.precision(2);
cout.setf(ios::showpoint);
cout.setf(ios::fixed,ios::floatfield);
cout << "Recapitulating, here are the figures:\n";
for (year = FirstYear; year <= FirstYear + Years; year++)
{
cout << year << ": $" << income[year] << "\n";
total += income[year];
}
cout << "busywork values: " << busywork;
} // end of try block
catch (LimitArE::SonOfBad & bi) // 1st handler
{
cout << "LimitArE exception:\n";