Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

OOP_C++ / 12

.htm
Скачиваний:
21
Добавлен:
02.02.2015
Размер:
26.73 Кб
Скачать

12 - Обработка исключений. Перегрузка операций Содержание     Предыдущее занятие     Следующее занятие

Занятие 12 Обработка исключений. Перегрузка операций 1 Генерация исключения Механизм работы с исключениями заключается в том, что функция, которая обнаружила ошибку и не может справиться с ней, запускает особую ситуацию, рассчитывая, что устранить проблему можно в той функции, которая прямо или опосредованно вызывала первую. Часть программы, которая будет обрабатывать исключение, может быть отделена и удалена от той части программы, которая обнаружила и возбудила исключение.

Запуск (генерация) исключения производится с помощью оператора throw. После этого оператора должно находиться выражение, которое определяет тип запускаемой особой ситуации. Выполнение этого оператора приводит к немедленной передаче управления подходящему обработчику объемлющего проверяемого блока, если такой есть. Например:

double f(double x) { if (x == 0) throw "Division by Zero"; return 1 / x; } В этом примере функция генерирует объект типа char*.

В процессе генерации исключения создается и инициализируется временная копия операнда оператора throw. Этот временный объект затем инициализирует параметр в обработчике исключения. Временный объект уничтожается, когда завершается выполнение обработчика исключения.

2 Обработка исключений Проверяемый блок начинается с ключевого слова try, за которым расположена последовательность операторов программы, помещенная в фигурные скобки:

try { . . . // Операторы проверяемого блока } Каждый обработчик начинается со служебного слова catch, за которым в круглых скобках идет тип особой ситуации, которую он перехватывает (или идентификатор данного типа), а потом в фигурных скобках - тело этого обработчика, например:

catch (int) // Если исключение имеет тип int, { . . . // то выполняем указанные действия } Если нужно перехватывать исключения всех типов, в скобках ставятся три точки:

catch (...) { . . . } в проверяемом блоке возникла особая ситуация, то управление немедленно передается в соответствующий этой ситуации по типу обработчик из числа обработчиков данного блока. Обработчики просматриваются в том порядке в каком они стоят за проверяемым блоком. Если среди обработчиков данного проверяемого блока подходящего нет, то исследуется объемлющий проверяемый блок. Если обработчик не найден, то программа прерывается.

Из обработчика можно передать ситуацию в объемлющий обработчик, если такой есть. Для этого используется оператор throw без выражения.

Список инициализации вместе с телом конструктора может быть помещен в блок try.

Для сопоставления исключения и его обработчика объект, указанный в выражении оператора throw, сравнивается по очереди с каждым обработчиком (с тем типом, который стоит в скобках). Считается, что сопоставление произошло, если выполняется одно из условий :

оба типа точно совпали;

тип обработчика является базовым классом для объекта, указанного в операторе throw;

тип обработчика является указателем, а объект, указанный в throw, можно к нему привести с помощью стандартного преобразования указателя.

После сопоставления с одним обработчиком последующие уже не проверяются.

После того, как исключение сгенерировано, управление не может возвратиться в точку генерации.

3 Классы исключений При работе с исключениями рекомендуется описывать типы исключений как вложенные классы в классе, в методах которого эти исключения генерируются:

class MyClass { public: class Not_Found {}; // пустое тело - нам нужен только тип class Bad_Data {}; void f(int i) { if (i < 0) // запускается исключение с временным объектом throw Bad_Data(); } }; void main() { try { MyClass m; m.f(-2); } catch (MyClass::Bad_Data) { // обработка исключения Bad_Data } catch (MyClass::Not_Found) { // обработка исключения Not_Found } } Особая ситуация перехватывается благодаря своему типу. Если нам нужно передать некоторую информацию из точки запуска в обработчик, то для этого ее следует поместить в создаваемый объект. Конструкция в скобках после служебного слова catch является описанием и она аналогична описанию формального параметра функции. В ней указывается каким может быть тип параметра (т.е. особой ситуации) и может задаваться имя для фактической, т.е. запущенной, особой ситуации.

Исключения, сгенерированные вне блока try, приводят к прерыванию программы.

Исключения обычно реализуют в виде иерархии классов для удобства группировки и последующего расширения. Обработчики, являющихся базовыми классами, должны располагаться в списке обработчиков после обработчиков, являющихся производными классами.

В С++ существует набор стандартных исключений. Например, bad_alloc генерируется операцией new и доступно через заголовочный файл <new>; исключение bad_cast может быть инициировано операцией dynamic_cast (заголовочный файл <typeinfo>); функция-элемент at может инициировать исключение out_of_range (заголовочный файл <stdexcept>) и т.д.

Все исключения стандартной библиотеки являются производными от класса exception и образуют иерархию, в которой выделяются логические ошибки (logic_error) и ошибки времени выполнения (runtime_error). Исключения, определенные пользователем, могут не иметь общего предка.

4 Список исключений функции Множество исключений, которые функция может запустить сама или которые могут быть запущены другими функциями, вызванными из нее, может задаваться с помощью списка исключений функции. Этот список находится после списка параметров и состоит из ключевого слова throw, после которого идет в круглых скобках список исключений, разделенных запятыми:

void f() throw (char *, MathErr&); Если список пустой

void ff() throw(); то это означает, что функция не может запускать особые ситуации вообще. Любое исключение не из списка вызывает функцию std::unexpected().

Каждое определение и объявление функции должно иметь спецификацию с одинаковым набором исключений.

5 Общие сведения о перегрузке операций При проектировании класса можно определить набор операций, которые можно выполнять над объектами. Определение операции для объекта класса или перечисления осуществляется с помощью так называемой операторной функции. Имя операторной функции состоит из служебного слова operator, за которым следует одна из предопределенных операций С++.

Операторная функция не обязательно должна быть функцией-элементом. Если она реализована как обычная функция, то по крайней мере один из ее параметров должен быть типа, определенного пользователем. Следовательно, предопределенное назначение оператора нельзя изменить для встроенных типов.

Проектировщик класса может определить (перегрузить) все операции, определенные в С++, кроме четырех: :: .* . ?: Нельзя задать новую операцию, например ** или <>. Предопределенные приоритеты операторов изменить нельзя. Должно сохраняться предопределенное число аргументов операции. Нельзя задавать значение параметров по умолчанию для операторных функций. Четыре операции ("+","-","*" и "&") могут использоваться и как бинарные, и как унарные.

6 Требования, предъявляемые к операторной функции Если операторная функция реализуется как функция-элемент класса, то считается, что левый операнд функции - объект, на который указывает this. Если требуется, чтобы левый операнд был другого типа, то операторная функция не может быть функцией-элементом.

Если операторная функция является элементом класса, количество ее параметров должно быть на 1 меньше количества операндов перегружаемой операции, так как в этом случае первым операндом является сам объект.

Некоторые операции - присваивание "=", индексация "[]", вызов функции "()" и выбор элемента "->" должны быть перегружены только как функции-элементы.

Операторные функции могут быть перегружены, если их можно различить по списку параметров.

Для перегрузки префиксных операций ++ и -- используются функции-элементы вида

X& X::operator++(); X& X::operator--(); Для перегрузки постфиксных операций ++ и -- используются функции-элементы вида

X X::operator++(int); X X::operator--(int); Операция присваивания перегружается в тех случаях, когда поэлементное копирование объектов приводит к ошибке.

При перегрузке операции обращения по индексу "[]" нужно учитывать, что нет ограничений ни на тип входного параметра, ни на значение, возвращаемое функцией.

Перегрузка операции вызова функции реализуется через функцию-элемент. Допускается произвольное количество параметров произвольных типов.

Операторные функции можно вызывать явно, например:

X x, x1; x = x.operator+(x1); 7 Операция выбора элемента по указателю Операцию косвенного обращения к элементу -> можно определить как унарную постфиксную операцию. Допустим, имеется класс X и класс Ptr:

class Ptr { // ... X* operator->(); }; объекты класса Ptr могут использоваться для доступа к членам класса X также, как для этой цели используются указатели:

void f(Ptr p) { p->m = 7; // (p.operator->())->m = 7; } Превращение объекта p в указатель p.operator->() никак не зависит от члена m, на который он указывает. Именно по этой причине operator->() является унарной постфиксной операцией.

8 Операции преобразования типов Существует особый вид операторных функций - операции преобразования типов, позволяющие преобразовывать объект в заданный тип по умолчанию. Операция преобразования должна быть реализована в виде функции-элемента и в общем случае она имеет следующий вид:

X::operator T(); // T - имя типа. Для операции преобразования нельзя задавать тип возвращаемого значения или указывать список формальных параметров. Например, благодаря операции преобразования допустимы следующие действия::

class X { int i; public: operator int() { return i; } . . . }; . . . X x; int k = x; // Используется значение элемента данных i int n = x + k; 9 Перегрузка операций ввода-вывода Операторы ввода и вывода реализуются как отдельные функции, а не члены класса. Такие функции обязательно должны получать в качестве первого параметра ссылку на соответствующий поток, а вторым параметром - ссылку на объект проектируемого класса. При перегрузке операции вывода вторым параметром должна быть ссылка на константный объект. Функции должны возвращать ссылку на поток, который передается ссылка на соответствующий поток:

ostream& operator<<(ostream& out, const X& x); istream& operator>>(istream& in, X& x); Часто такие функции объявляют друзьями проектируемого класса.

10 Примеры программ 10.1 Пример генерации и обработки исключений Для спроектированного ранее класса Array необходимо реализовать операцию индексации, использующую запуск исключения.

class Array { . . . public: class OutOfBounds { }; double& at(int index); . . . }; double& Array::at(int index) { if (index < 0 || index >= size) throw OutOfBounds(); return pa[index]; } В функции main() обращение элемента по индексу можно включить в блок обработки особых ситуаций:

int main(int argc, char* argv[]) { Array b; b.addElem(11); b.addElem(12.5); try { b.at(10) = 35; // Выход за границы массива } catch (Array::OutOfBounds) // или catch (...) { cout << "Error!" << endl; } return 0; } В объявлении и определении функции-элемента можно добавить список исключений. Кроме того, в классе OutOfBounds можно предусмотреть хранение данных о неправильном индексе:

class Array { . . . public: class OutOfBounds { int index; public: OutOfBounds(int i) : index(i) { } int getIndex() const { return index; } }; double& at(int index) throw (OutOfBounds); . . . }; double& Array::at(int index) throw (Array::OutOfBounds) { if (index < 0 || index >= size) throw OutOfBounds(index); // Передаем неправильный индекс return pa[index]; } В функции main() можно предусмотреть соответствующие изменения:

int main(int argc, char* argv[]) { Array b; b.addElem(11); b.addElem(12.5); try { b.at(10) = 35; // Выход за границы массива } catch (Array::OutOfBounds e) // используем экземпляр исключения { cout << "Bad index: " << e.getIndex() << endl; } return 0; } Список исключений в объявлении полезен для конструирования программы, использующую стандартные (библиотечные) функции, реализация которых может быть недоступна пользователю.

10.2 Пример реализации перегрузки операций Для ранее спроектированного класса Point (точка на плоскости) можно реализовать операции сравнения, сложения и вывода в поток.

Для реализации функции сравнения можно создать функцию-элемент, тело которой аналогично ранее рассмотренной функции isEqual(). Для повышения надежности параметр (второй операнд операции) должен быть константной ссылкой. Кроме того, поскольку функция не должна менять значений элементов данных класса, ее целесообразно реализовать с квалификатором const:

bool Point::operator==(const Point& p) const { return this == &p || p.x == x && p.y == y; } Аналогично можно реализовать операцию сложения:

Point Point::operator+(const Point& p) const { Point new_p; new_p.x = x + p.x; new_p.y = y + p.y; return new_p; } Операцию вывода в поток нельзя реализовать как функцию-элемент, а только как отдельную (дружественную) функцию:

ostream& operator<<(ostream& out, const Point& p) { return out << "x= " << p.x << " y= " << p.y; } Весь текст программы будет иметь вид:

// Класс "точка на плоскости" с перегруженными операциями #include <iostream.h> class Point { friend ostream& operator<<(ostream& out, const Point& p); private: double x, y; public: Point() { x = y = 0; } Point(double newX, double newY) { x = newX; y = newY; } int operator==(const Point& p) const; Point operator+ (const Point& p) const; }; ostream& operator<<(ostream& out, const Point& p) { return out << "x= " << p.x << " y= " << p.y; } int Point::operator==(const Point& p) const { return this == &p || p.x == x && p.y == y; } Point Point::operator+ (const Point& p) const { Point new_p; new_p.x = x + p.x; new_p.y = y + p.y; return new_p; } int main() { Point p1(1, 1), p2(2, 2), p3(1, 1); cout << "\np1: (" << p1 << ") p2: (" << p2 << ") p3: (" << p3 << ")\n"; cout << "(p1 == p1) = " << (p1 == p1) << " (p1 == p2) = " << (p1 == p2) << " (p1 == p3) = " << (p1 == p3) << endl; p3 = p1 + p2; cout << "p1 + p2 = (" << p3 << ")\n"; return 0; } 10.3 Перегрузка операций для класса Array Для ранее спроектированного класса Array (массив) необходимо реализовать перегрузку операций вывода в поток, присваивания, проверки на равенство и доступ к элементам через операцию индексации.

Операции ввода и вывода реализуются дружественными функциями:

ostream& operator a.pa[i]; return in; } Операцию обращения по индексу необходимо реализовать функцией-элементом:

double& Array::operator[](int index) { if (index < 0 || index >= size) throw OutOfRange(); return pa[index]; } Функция возвращает ссылку на элемент массива, благодаря чему возможно ее использование для присваивания элементу нового значения. Для того, чтобы элементы были доступны только для чтения, функция должна возвращать значение, а не ссылку.

Реализация функции присваивания отличается от конструктора копирования тем, что необходимо проверять, был ли создан массив ра, и освобождать память, которую занимали элементы этого массива.

const Array& Array::operator=(const Array& a) { if (&a != this) { if (pa) delete [] pa; size = a.size; pa = new double [size]; for (int i = 0; i < size; i++) pa[i] = a.pa[i]; } return *this; } Операция сравнения последовательно проверяет: не совпал ли данный объект присваиваемым, одинаковы ли у них размеры и совпадают ли у них элементы:

bool Array::operator==(const Array& a) const { if (&a == this) return true; if (size != a.size) return false; for (int i = 0; i < size; i++) if (pa[i] != a.pa[i]) return false; return true; } Соответствующие изменения вносятся в описание класса:

// Класс-исключение class OutOfRange{}; // Класс, реализующий массив class Array { friend ostream& operator(istream& in, Array& a); private: double *pa; int size; public: Array(); // Реализовано ранее Array(int n); // Реализовано ранее Array(Array& arr); // Реализовано ранее ~Array(); // Реализовано ранее void addElem(double elem); // Реализовано ранее double& operator[](int index); const Array& operator=(const Array& a); bool operator==(const Array& a) const; int getSize() const { return size; } }; В функции main() осуществляется тестирование перегруженных операций:

int main(int argc, char* argv[]) { Array a; a.addElem(11); a.addElem(12.5); for (int i = 0; i < a.getSize(); i++) { cout << a[i]; // Обращение по индексу cout << ' '; } cout << endl; Array b = a; // Конструктор копирования b[1] = 35; cout << b << endl; // Оператор вывода cout << endl; b = a; // Оператор присваивания if (b == a) // Оператор сравнения cout << b << endl; cin.get(); return 0; } 11 Задания на самостоятельную работу Задание 1 Видоизменить спроектированный ране класс, представляющий матрицу, таким образом, чтобы его функции-элементы запускали исключения. Необходимые классы исключений реализовать как вложенные в проектируемый класс. Протестировать работу в функции main().

Задание 2 Добавить к спроектированному ранее классу, представляющему двумерный массив вещественных чисел, функции для реализации перегрузки операторов. Обязательно предусмотреть средства для перегрузки операций вывода в поток, присваивания, проверки на равенство и доступ к элементам через операцию (). Протестировать работу массива в функции main().

 

Содержание     Предыдущее занятие     Следующее занятие

 

© 2001 - 2006 Иванов Л.В.

Соседние файлы в папке OOP_C++