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

Из конструктора и деструктора нельзя возвращать значение. Механизм исключений дает возможность сообщить об ошибке, возникшей в конструкторе или деструкторе объекта.

Пример: класс Vector, в котором ограничивается количество запрашиваемой памяти:

class Vector{

public:

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

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

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

if (n<0 || 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.

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

Тема 2.16

Преобразование типов: преобразование с помощью функций языка С++

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

Операция const_castслужит для удаления модификатора const.

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

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

Указанный тип должен быть таким же, как и тип выражения, за исключением модификатора const (обычно это указатель).

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

Пример:

void print (int *p) { // Функция не изменяет значение *р

cout << *р;

}

const int *p;

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

void print (int *p) { // Функция не изменяет значение *р

cout << *р;

}

const int *p;

print(const_cast<int*>p); // Верно, указатель на константу преобразован в обычный

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

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

· целыми типами,

· целыми и вещественными типами,

· целыми и перечисляемыми типами.

Кроме того, операция static_cast используется при наследовании.

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

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

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

Пример:

float f = 100;

int i = static_cast <int> (f); // целые и вещественные имеют различное внутреннее представление

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

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

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

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

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

Пример:

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

long l = reinterpret_cast <long>(p);

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

Операция 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 должен быть полиморфного типа, т.е. иметь хотя бы один виртуальный метод.

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

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

Результат примепения операции dynamic_cast к указателю всегда требуется проверять явным образом.

Для использования механизма RTTI необходимо подключить к программе заголовочный файл typeinfo.h и установить соответствующий режим компилятора.

Пример:

Описан полиморфный базовый класс В и производный от него класс С, в котором определена функция f2. Операция dynamic_cast с проверкой результата преобразования используется для того, чтобы вызывать f2 из функции demo, определенной в глобальной области видимости, только в случае, когда последней передается указатель на объект производного класса.

#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 <typeinfo.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 fl ( ) { ... };

};

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;

}

Наши рекомендации