Int main()
{ rep:
try {
ArrayDbE Team(Players);
cout<<"Enter percentages for your "<<Players
<<" top players as a decimal fraction:\n";
int player;
for (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 percentagrs:\n";
for (player=0; player<=Players+1; ++player) // провокує BadIndex
cout<<"Player #"<<(player+1)<<": "<<100.0*Team[player]<<"%\n";
}
catch (ArrayDbE::BadIndex & bi)
{ cout<<"\nArrayDbE exception: "<<bi.badInd<<" is a bad index value\n"
<<"Information from the exception object:\n";
bi.report();
}
catch (ArrayDbE::BadSize)
{ cout<<"\nArrayDbE exception: bad size of the array.\nEnter a valid size: ";
cin>>Players; goto rep;
}
cout << "Bye!\n";
return 0;
}
Зверніть увагу на визначення типу винятка в catch-блоках – все правильно, класи винятків оголошено всередині класу, тому потрібно використати кваліфікатор імені. Як і в попередньому прикладі, клас BadSize порожній, використовують тільки його ім’я. Зате клас BadIndex наділено і полями, і методами! Традиційно їх роблять відкритими.
Після захищеного блока розташовано два блоки перехоплення: кожен спеціалізується на винятку «свого» типу. Щоб перехоплювати одним блоком будь-які винятки, використовують конструкцію catch(…) – «перехопити виняток довільного типу».
Що буде з класами винятків, якщо ArrayDbE стане батьківським для нового класу? Те саме, що й з усіма іншими елементами – вони успадкуються дочірнім класом. Покажемо це на прикладі. Породжений клас LmitArE дозволяє користувачеві встановлювати початковий індекс елементів масиву відмінний від нуля. Додає він також і з опрацюванням помилок: тепер інакше розпізнається вихід індекса за допустимі межі. З цією метою оголошено новий клас-виняток.
#include "arraydbe.h"
class LimitArE: public ArrayDbE {
public:
class SonOfBad: public ArrayDbE::BadIndex { // новий виняток
public: // містить більше даних
int l_lim, u_lim;
SonOfBad(int i, int l, int u): BadIndex(i), l_lim(l), u_lim(u) {}
void report() const;
};
private:
unsigned int low_bnd; // нижня межа масиву
protected:
virtual void ok(int) const throw(SonOfBad&); // перевірка індекса
public:
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) {}
void new_lb(int lb) { low_bnd = lb; }
int lbound() const { return low_bnd; }
int ubound() const { return arSize()+low_bnd-1; }
double& operator[](int) throw(ArrayDbE::BadIndex&);
const double& operator[](int) const throw(ArrayDbE::BadIndex&);
};
// новий виняток інакше ніж батько рапортує про себе
void LimitArE::SonOfBad::report() const
{ std::cerr << "Out of bounds index value: " << badInd
<< "\n actual bounds are: "<<l_lim<<':'<<u_lim<<'\n';
}
// спеціалізований метод, що перевіряє межі та генерує виняток
void LimitArE::ok(int i) const throw(SonOfBad&)
{ unsigned size = arSize();
if (i<low_bnd || i>=size+low_bnd) throw SonOfBad(i,low_bnd,low_bnd+size-1);
}
// оператори індексування
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];
}
Клас LmitArE успадкував від ArrayDbE винятки BadSize та BadIndex і визначив новий виняток SonOfBad для уточнення повідомлення про неправильний індекс. Оскільки він описує ту ж помилку, що й BadIndex, тільки по-новому, його оголошено підкласом BadIndex.
Класи винятків не обов’язково оголошувати всередині інших класів. Ми могли б визначити цілком незалежну ієрархію класів BadIndex SonOfBad і використати їх для генерування винятків у ArrayDbE LmitArE. Таку архітектуру використовують у тих випадках, коли одні і ті ж винятки можуть генерувати різні не споріднені класи.
Розглянемо уважніше, як в класі LmitArE визначено оператори індексування. Самі вони не генерують винятків, натомість звертаються за послугами до спеціально оголошеного методу ok(i). Цей метод запускає (якщо задано неправильний індекс) екземпляр класу SonOfBad. Оператори індексування передають виняток далі за лопомогою посилання на BadIndex. Все правильно, кожен SonOfBad «is a» BadIndex, тому ArrayDbE::BadIndex& в заголовках операторів індексування є поліморфними посиланнями.
А як відбувається опрацювання винятку SonOfBad? Припустимо, що функція main() звернулась до екземпляра класу LmitArE з повідомленням [k]. Відбудеться такий ланцюжок викликів:
main() → LmitArE::operator[](k) → LmitArE::ok(k).
Якщо значення k неправильне, метод ok згенерує виняток і запустить процес розгортання стека, керування передаватиметься ланцюжком викликів у зворотньому напрямку від функції, що запустила виняток, до функції, яка може його опрацювати: throw запускає виняток, завершує виконання ok, звільняє всі її локальні змінні; operator[] не містить блоків опрацювання винятків, тому завершує роботу, всі його локальні змінні вилучаються зі стека; керування отримує main, яка мала б перехопити виняток, якщо цього не станеться, програма завершиться аварійно. Таким чином, запущений виняток може завершити виконання однієї або декількох функцій.
Розглянемо приклад використання LmitArE:
#include <iostream>
#include "arraydbe.h"
#include "limarre.h"
using namespace std;
const int Years = 3;
const int FirstYear = 2007;
