
- •18.Классы для работы с векторами и матрицами.
- •1.1 Описание программы, матрицы
- •1.3 Microsoft Visual Studio Express
- •1.4 Стандартная библиотека шаблонов (stl)
- •1.5 Vector
- •1.6 Перегрузка операторов
- •2. Проектирование и этапы разработки
- •2.1 Постановка задачи
- •Int lbound;
- •Int ubound;
- •1) Создается и запоминается копия объекта (переменной). Это означает, что
- •2) Разматывает стек, вызывая деструкторы локальных объектов, выходящих
- •3) Передается управление ближайшему catch-обработчику, совместимому с
- •Void f1(void) {
- •Void f2( Vector& V ) {
- •Void f() {
- •Void g() {
- •Void use_file( const char* filename ) {
- •Void use_file( const char* filename ) {
- •Void get_resources( ) {
- •Void use_file( const char* filename )
- •X( const char* aa, const char* bb )
- •Init(), то выделенная память не будет освобождена, поскольку объект полностью
- •X( int s ) : ip( s ) { init( ); }
- •Void f( int a ) throw( Range, Size, int, char* )
- •Void f( int a ) {
- •Int g( void ) throw( ); // функция не заявляет каких-либо исключений
- •Void f( void ) throw( int ){ throw “This error message has type char* ”; }
- •Void rethrow( ) { throw; }
- •Void network_g( )
1) Создается и запоминается копия объекта (переменной). Это означает, что
если в точке генерации исключения оказывается недоступен копирующий
конструктор (например, он не является public, а исключение заявляется не
функцией-другом), то возникает сообщение об ошибке.
2) Разматывает стек, вызывая деструкторы локальных объектов, выходящих
из области видимости
10
3) Передается управление ближайшему catch-обработчику, совместимому с
типом выброшенного исключения. При этом копия объекта-исключения
передается, если это предусмотрено, обработчику в качестве параметра.
Обработчик считается найденным, а исключение обработанным, если:
a) тип исключения соответствует типу, ожидаемому в обработчике.
Переменной (объекту) типа T соответствует обработчик,
перехватывающий T, const T, T&, const T&.
b) тип обработчика является публичным базовым классом для
заявленного исключения
c) обработчик ожидает указатель, и исключение является указателем,
который может быть преобразован к типу обработчика по
стандартным правилам преобразования указателей
d) встретился обработчик по умолчанию. Обработчик по умолчанию
вызывается для исключения любого типа и имеет вид
catch( ... ){
// тело обработчика
}
Очевидно, что обработчик по умолчанию должен располагаться
последним среди обработчиков данного try-блока.
Рассмотрим пример. Пусть класс Vector должен обнаруживать и
сообщать об ошибках двух видов – ошибках индексации и ошибках
распределения памяти:
class Vector {
private:
int lbound, ubound;
int *v;
public:
class Range{};
class Memory{};
// . . . . .
Vector(int);
int& operator[](int);
// . . . . .
};
Vector::Vector(int size) {
if(!(v=new int[size])) throw Memory();
else { lbound=0; ubound = size-1; }
}
11
int& Vector::operator[](int i) {
if( i < lbound || ubound < i ) throw Range();
else return v[i-lbound];
}
Пользователь класса Vector может различить два исключения, включив два
обработчика:
void f(void) {
try{
use_vectors();
}
catch( Vector::Range ){
// тело обработчика ошибки индексации
}
catch( Vector::Memory ){
// тело обработчика ошибки выделения памяти
}
}
Если удается пройти обработчик, то выполняется код за обработчиками.
Например:
void f(void)
{
try{
use_vectors();
// на эту часть кода мы попадаем,
// если не возникло исключения при
// вызове функции use_vectors()
}
catch( Vector::Range ){
// здесь исправляем индекс
// и пытаемся продолжить
f();
}
catch( Vector::Memory ){
cerr << “Memory error. Продолжение невозможно.\n”;
exit(1);
}
12
// на эту часть кода мы попадаем, если не возникло
// исключения, или после обработчика исключения Range.
}
Функция не обязана обрабатывать все возможные исключения. Например:
Void f1(void) {
try{
f2(v);
}
catch( Vector::Memory ){
// . . . . .
}
}
Void f2( Vector& V ) {
try{
use_vectors();
}
catch( Vector::Range ){
// . . . . .
}
}
Здесь f2() перехватывает ошибки Range в use_vectors() и пропускает ошибки
Memory, которые обрабатываются в f1().
Исключение считается обработанным сразу после входа в обработчик.
Поэтому исключение, возникающее в обработчике должно обрабатываться
программным кодом, вызывающим try-блок. Пусть ExeptionType имя класса,
тогда 108яХыыкод
try{
do_something();
}
catch( ExeptionType ){
// . . . . .
throw ExeptionType();
}
не приводит к возникновению бесконечного цикла. Обработчики ситуаций
могут быть вложенными. Например,
try {
// . . . . .
}
13
catch( type ) {
try {
// код, в котором возможно
// возникновение ситуации типа type
}
catch( type ) {
// . . . . .
}
}
(Объясните, почему не происходит зацикливания в этом примере). Заметим, что
такой стиль программирования усложняет понимание программного кода и его
следует избегать.
Обычно исключение не только сигнализирует об ошибке, но и содержит
некоторую информацию о происшедшем. Например, при нарушении
индексации полезно передать значение индекса, вызвавшее ситуацию:
class Vector {
// . . . . .
public:
class Range{
public:
int index;
Range(int i) : index(i){}
};
// . . . . .
int& operator[](int);
// . . . . .
};
int& Vector::operator[ ](int i) {
if( i < lbound || ubound < i ) throw Range(i);
else return v[i-lbound];
}
void f( Vector& v ) {
// . . . . .
try{
use_vectors(v);
}
catch( Vector::Range r ){
14
cerr << “Bad index: “ << r.index << ‘\n’;
// . . . . .
}
// . . . . .
}
ИСКЛЮЧЕНИЯ В ШАБЛОНАХ.
Рассмотрим пример
template<class T>
class Vector {
private:
T* v;
int lbound;
int ubound;
public:
class Range{ };
// . . .
T& operator[]( int ) {
if( i < lbound || ubound < i ) throw Range();
return v[i-lbound];
};
// . . .
}; // class Vector
Здесь в шаблоне класса содержится тип, используемый для исключений.
Каким образом это проявляется в параметризованных классах, полученных из
этого шаблона? Очевидно, в каждом таком классе мы имеем свой тип
исключения, для которого должен использоваться свой обработчик:
void f( Vector<int>& vi, Vector<double>& vd ) {
try {
// . . . . .
}
catch( Vector<int>::Range ) {
// . . . . .
}
catch( Vector<double>::Range ) {
// . . . . .
}
}
15
Если нам требуется использовать один тип исключения для всех
параметризованных классов, то следует просто сделать класс Range внешним.
ИЕРАРХИЯ ИСКЛЮЧЕНИЙ
Часто исключения естественным образом группируются в семейства.
Например, с ситуациями, возникающими в функциях математической
библиотеки, можно связать семейство исключений MathError, включающее
исключения Overflow, Underflow, Zerodivide и т.п.
Один из способов такой группировки состоит в определении
перечислимого типа данных MathError с соответствующими значениями:
enum MathError { Overflow, Underflow, Zerodivide, /*. . . . .*/ };
// . . . . .
try {
// . . . . .
MathError m;
throw m;
}
catch( MathError m ) {
switch (m) {
case Overflow : /* . . . . . */ break;
case Underflow : /* . . . . . */ break;
case Zerodivide : /* . . . . . */ break;
default : /* . . . . . */;
}
}
Объектно-ориентированный подход подразумевает использование
механизмов наследования и виртуальных функций:
class MathError { };
class Overflow : public MathError { };
class Underflow : public MathError { };
class Zerodivide : public MathError { };
// . . . . .
try {
// . . . . .
}
catch( Overflow ) {
// обработка Overflow и его потомков
16
}
catch( MathError ) {
// обработка любого MathError, кроме Overflow и его потомков
}
Здесь все исключения, кроме Overflow, обрабатываются единообразно, как
MathError. Если требуется полиморфизм, то исключение в обработчик можно
передавать по ссылке, либо использовать указатели.
Наградой за построение иерархии исключений является ее
технологичность при сопровождении больших программ. Представьте себе,
например, обработку всех исключений для стандартной математической
библиотеки. Без группировки на основе наследования это приводит к длинным
последовательностям обработчиков:
try {
// . . . . .
}
catch( Overflow ) { /* . . . . . */ }
catch( Underflow ) { /* . . . . . */ }
catch( Zerodivide ) { /* . . . . . */ }
// . . . . .
Весьма часто реакция на разные исключения будет одинаковой. Такая
ситуация вызывает раздражение из-за дублирования кода и содержит опасность
пропуска некоторых исключений в последовательности обработчиков. Кроме
того, добавление нового исключения требует модификации и перекомпиляции
уже работающих программ, что чревато внесением ошибок в ранее отлаженный
код и требует его нового тестирования. Представьте себе, например, что
добавлено исключение в стандартную библиотеку. Тогда модули, в которых
должны обрабатываться все исключения, должны быть модифицированы и
перекомпилированы. Но для больших библиотек это неприемлемо, поскольку
исходный код библиотеки или некоторой ее части может оказаться
недоступным.
Использование наследования позволяет вводить исключения,
принадлежащие различным группам. Например:
class network_file_err : public network_err, public file_system_err {
// . . . . .
};
Исключение network_file_err может быть отловлено как функциями,
работающими с сетевыми исключениями:
17