Исключения в конструкторах и деструкторах



Язык C++ не позволяет возвращать значение из конструктора и деструктора. Механизм исключений дает возможность сообщить об ошибке, возникшей в конструкторе или деструкторе объекта. Для иллюстрации создадим класс Vector, в котором ограничивается количество запрашиваемой памяти:

class Vector

{

public:

class Size{}; // Класс исключения

enum {max = 32000}; // Максимальная длина вектора

Vector(int n) // Конструктор

{

         if (n<0 II n>max)

            { throw Size(); ... }

         ...

      }

 

При использовании класса Vector можно предусмотреть перехват исключений типа Size:

 

try

{

Vector *р = new Vector(i);

} catch(Vector::Size)

{

... // Обработка ошибки размера вектора

}

 

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

 

Иерархии исключений

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

 

class Matherr{};

class Overflow: public Matherr{}; // Переполнение

class Underflow: public Matherr{}: // Исчезновение порядка

class ZeroDivide: public Matherr{}; // Деление на ноль

 

Для представления ошибок ввода/вывода могут использоваться следующие классы:

 

class IOerr{};

class Readerr: public IOerr{}; // Ошибка чтения

class Writerr: public IOerr{}; // Ошибка записи

class Seekerr: public IOerr{}: // Ошибка поиска

 

В зависимости от обстоятельств можно использовать либо обработчик исключений базового класса, который будет перехватывать и производные исключения, либо собственные обработчики производных классов. Существует ряд стандартных исключений, которые генерируются операциями или функциями C++. Все они являются производными от библиотечного класса exception, описанного в заголовочном файле <stdexcept>. Например, операция new при неудачном выделении памяти генерирует исключение типа bad_alloc. Программист может определить собственные исключения, производные от стандартных.

ПРЕОБРАЗОВАНИЯ ТИПОВ

При выполнении программы производятся явные и неявные преобразования величин из одного типа в другой. Для выполнения явных преобразований типа в C++ существует целая группа операций — const_cast. dynamic__cast, reinterpret_cast и static_cast, а также операция приведения типа, унаследованная из языка С. Для начала рассмотрим ее подробнее.

 

Операция приведения типов в стиле С

Операция может записываться в двух формах:

тип (выражение)

(тип) выражение

 

Результатом операции является значение заданного типа, например:

 

int а = 2;

float b = 6.8;

printf("%lf %d", double(a), (int) b);

 

Величина a преобразуется к типу double, a переменная b — к типу int с отсечением дробной части, в обоих случаях внутренняя форма представления результата операции преобразования иная, чем форма исходного значения. Необходимость в преобразовании типа возникает, например, в случае, когда функция возвращает указатель на тип void, который требуется присвоить переменной конкретного типа для последующего выполнения с ней каких-либо действий:

 

float *q = (float *) malloc(100*sizeof(float));

 

Явное преобразование типа является источником возможных ошибок, поскольку вся ответственность за его результат возлагается на программиста. Поэтому в C++ введены операции, позволяющие выполнять частичный контроль выполняемых преобразований или сделать намерения программиста более явными для понимания. Рассмотренное выше преобразование в стиле С оставлено в C++ только для нисходящей совместимости, и использовать его не рекомендуется. В зависимости от вида требуемого преобразования необходимо использовать соответствующую ему операцию приведения типа.

В ранних версиях компиляторов C++ операции приведения типа const_cast, dynam1c_cast, reinterpret_cast и static_cast не поддерживаются.

Операция const _ cast

Операция служит для удаления модификатора const. Как правило, она используется при передаче в функцию константного указателя на место формального параметра, не имеющего модификатора const. Формат операции:

 

const_cast <тип> (выражение)

 

Обозначенный тип должен быть таким же, как и тип выражения, за исключением модификатора const. Обычно это указатель. Операция формирует результат указанного типа. Необходимость введения этой операции обусловлена тем, что программист, реализующий функцию, не обязан описывать не изменяемые в ней формальные параметры как const, хотя это и рекомендуется. Правила C++ запрещают передачу константного указателя на место обычного. Операция const_cast введена для того, чтобы обойти это ограничение. Естественно, функция не должна пытаться изменить значение, на которое ссылается передаваемый указатель, иначе результат выполнения программы не определен. Пример:

 

void print(int *р)

{

// Функция не изменяет значение *р

cout « *р;

}

const int *р;

 

...

 

/* print(p); Ошибка, поскольку р объявлен как указатель на константу */

 

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

Операция dynamic_cast

Операция применяется для преобразования указателей родственных классов иерархии, в основном — указателя базового класса в указатель на производный класс, при этом во время выполнения программы производится проверка допустимости преобразования. Формат операции:

 

dynamic_cast <тип *> (выражение)

 

Выражение должно быть указателем или ссылкой на класс, тип — базовым или производным для этого класса. После проверки допустимости преобразования в случае успешного выполнения операция формирует результат заданного типа, в противном случае для указателя результат равен нyлю, а для ссылки порождается исключение bad_cast. Если заданный тип и тип выражения не относятся к одной иерархии, преобразование не допускается. Преобразование из базового класса в производный называют понижающим (downcast), так как графически в иерархии наследования принято изображать производные классы ниже базовых. Приведение из производного класса в базовый называют повышающим (upcast), а приведение между производными классами одного базового или, наоборот, между базовыми классами одного производного — перекрестным (crosscast).

 

Повышающее преобразование

Выполнение с помощью операции dynamic_cast повышающего преобразования равносильно простому присваиванию:

 

class В{ / * ... * / };

class С: public В{ /* ... */ };

С* с = new С();

В* b = dynamic_cast<B*>(c); // Эквивалентно В* b = с;

 

Понижающее преобразование

Чаще всего операция dynamic_cast применяется при понижающем преобразовании — когда компилятор не имеет возможности проверить правильность приведения. Производные классы могут содержать функции, которых нет в базовых классах. Для их вызова через указатель базового класса нужно иметь уверенность, что этот указатель в действительности ссылается на объект производного класса. Такая проверка производится в момент выполнения приведения типа с использованием RTTI (run-time type information) — «информации о типе во время выполнения программы». Для того чтобы проверка допустимости могла быть выполнена, аргумент операции dynamic_cast должен быть полиморфного типа, то есть иметь хотя бы один виртуальный метод. Для полиморфного объекта реализация операции clynamic_cast весьма эффективна, поскольку ссылка на информацию о типе объекта заносится в таблицу виртуальных методов, и доступ к ней осуществляется легко. С точки зрения логики требование, чтобы объект был полиморфным, также оправдано: ведь если класс не имеет виртуальных методов, его нельзя безопасным образом использовать, не зная точный тип указателя. А если тип известен, использовать операцию dynam1c_cast нет необходимости. Результат применения операции dynamic_cast к указателю всегда требуется проверять явным образом. В приведенном ниже примере описан полиморфный базовый класс В и производный от него класс С, в котором определена функция f2. Для того чтобы вызывать ее из функции demo только в случае, когда последней передается указатель на объект производного класса, используется операция dynamic_cast с проверкой результата преобразования:

 

#include <iostream.h>

#include <typeinfo.h>

class B

{

public: virtual void f1() {};

};

class C: public B

{

public: void f2() {cout « "f2"; };

};

void demo(B* p)

{

C* с = dynamic_cast<C*>(p);

if (c)

c->f2();

else

cout « "Передан не класс С":

}

int main()

{

В* b = new В;

demo(b); // Выдается сообщение "Передан не класс С"

С* с = new С;

demo(c); // Выдается сообщение "f2" (правильно)

return 0;

}

 

При использовании в этом примере вместо dynamic_cast приведения типов в стиле С, например:

 

С* с = (С*) р;

 

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

 

#include <iostream.h>

#include <type1nfo.h>

class A

{

public: virtual ~A(){};

}

class B: public virtual A{};

class C: public virtual A{};

class D: public B, public C{};

void demo(A *a)

{

D* d = dynamic_cast<D*>(a);

if (d) { ... }

}

int main()

{

D *d = new D;

demo(d);

return 0;

}

 

Преобразование ссылок

Для аргумента-ссылки смысл операции преобразования несколько иной, чем для указателя. Поскольку ссылка всегда указывает на конкретный объект, операция dynamic_cast должна выполнять преобразование именно к типу этого объекта. Корректность приведения проверяется автоматически, в случае несовпадения порождается исключение bad_cast:

 

#include <iostream.h>

#include <typeinfo.h>

class B

{

public: virtual void f1 () { ... };

};

class C: public B

{

public: void f2() { ... };

};

void demo(B& p)

{

try

{

C& с = dynamic_cast<C&>(p);

c.f2();

}

catch(bad_cast)

{

...

}

}

int main()

{

B* b = new B;

demo(*b); // Порождается исключение

С* с = new С;

demo(*c); // Правильно return 0;

}

 

Перекрестное преобразование

Операция dynamic_cast позволяет выполнять безопасное преобразование типа между производными классами одного базового класса, например:

 

#include <iostream.h>

#include <typeinfo.h>

class B

{

public: virtual void f1 () {};

};

class C: public B

{

public: void f2(){ ... };

};

class D: public B{ ... };

void demo(D* p)

{

C* с = dynamic_cast<C*>(p);

if (c)

c->f2();

else

cout « " не выполнено ":

}

int main()

{

B* b = new C;

demo((D*)b);

return 0;

}

 

Классы С и D являются производными от класса В. Функции demo передается указатель на класс D, являющийся на самом деле указателем на «братский» для него класс С, поэтому динамическое преобразование типа из D в С в функции demo завершается успешно.

При необходимости можно осуществить преобразование между базовыми классами одного производного класса, например:

 

#include <iostream.h>

#include <typeinfo.h>

class B

{

public: virtual void f1 () { ... };

};

class C

public: virtual void f2(){ ... };

};

class D: public B, public C{};

void demo(B* b)

{

С* с = dynamic_cast<C*>(b);

if (c)

c->f2();

}

int main()

{

D* d = new D;

demo(d);

return 0;

}

 

Класс D является потомком В и С, поэтому содержит методы обоих классов. Если в функцию demo передается на самом деле указатель не на В, а на D, его можно преобразовать к его второму базовому классу С.

Операция static_cast

 

Операция static_cast используется для преобразования типа на этапе компиляции между:

1) целыми типами;

2) целыми и вещественными типами;

3) целыми и перечисляемыми типами;

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

Формат операции:

 

static_cast <тип> (выражение)

 

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

 

float f = 100;

int i = static_cast<int>(f);

// Целые и вещественные имеют различное внутреннее представление

 

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

 

class В{};

class С: public В{};

С с;

В *bр = static_cast<B*>(c); // Производный -> базовый

В b;

С &ср = static_cast<C&>(b); // Базовый -> производный

 

Преобразование выполняется при компиляции, при этом объекты могут не быть полиморфными. Программист должен сам отслеживать допустимость дальнейших действий с преобразованными величинами. В общем случае использование для преобразования указателей родственных классов иерархии предпочтительнее использовать операцию dynamic_cast. В этом случае если преобразование возможно на этапе компиляции, генерируется тот же код, что и для static_cast. Кроме того, dynamic_cast допускает перекрестное преобразование, нисходящее приведение виртуального базового класса и производит проверку допустимости приведения во время выполнения.

Операция reinterpret_cast

Операция reinterpret_cast применяется для преобразования не связанных между собой типов, например, указателей в целые или наоборот, а также указателей типа void* в конкретный тип. При этом внутреннее представление данных остается неизменным, а изменяется только точка зрения компилятора на данные. Формат операции:

 

reinterpret_cast <тип> (выражение)

 

Результат операции имеет указанный тип, который может быть ссылкой, указателем, целым или вещественным типом. Пример:

 

char *р = re1nterpret_cast <char*>(malloc(100));

long l = reinterpret_cast <long>(p);

 

Различие между static_cast и reinterpret_cast позволяет компилятору производить минимальную проверку при использовании static_cast, а программисту — обозначать опасные преобразования с помощью reinterpret_cast. Результат преобразования остается на совести программиста.


Дата добавления: 2019-09-13; просмотров: 412; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!