Исключения в конструкторах и деструкторах
Язык 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; Мы поможем в написании вашей работы! |
Мы поможем в написании ваших работ!