Lection20

advertisement
Лекция 20
Преобразования типов
1
• При выполнении программы производятся
явные и неявные преобразования величин из
одного типа в другой.
• Неявные преобразования выполняются в
соответствии с правилами.
• Для выполнения явных преобразований типа
в С++ существует целая группа операций —
const_cast, dynamic_cast, reinterpret_cast и
static_cast, а также операция приведения
типа, унаследованная из языка С.
2
Операция приведения типов в стиле С
Операция может записываться в двух
формах:
тип (выражение)
(тип) выражение
Результатом операции является значение
заданного типа, например:
int a = 2;
float b = 6.8;
printf("%lf %d", double (a), (int) b);
3
Величина a преобразуется к типу double, а
переменная b — к типу int с отсечением
дробной части, в обоих случаях внутренняя
форма представления результата операции
преобразования иная, чем форма исходного
значения.
• Необходимость в преобразовании типа
возникает, например, в случае, когда функция
возвращает указатель на тип void, который
требуется присвоить переменной конкретного
типа для последующего выполнения с ней
каких-либо действий:
4
• float *q = (float *) malloc(100 * sizeof(float));
• Явное преобразование типа является
источником возможных ошибок, поскольку вся
ответственность за его результат возлагается
на программиста.
• Поэтому в С++ введены операции,
позволяющие выполнять частичный контроль
выполняемых преобразований или сделать
намерения программиста более явными для
понимания.
• Рассмотренное выше преобразование в стиле
С оставлено в С++ только для нисходящей
совместимости, и использовать его не
рекомендуется. В зависимости от вида
требуемого преобразования необходимо
использовать соответствующую ему операцию
приведения типа.
5
Операция const_cast
• Операция служит для удаления модификатора
const. Как правило, она используется при
передаче в функцию константного указателя на
место формального параметра, не имеющего
модификатора const. Формат операции:
• const_cast <тип> (выражение)
• Обозначенный тип должен быть таким же, как и
тип выражения, за исключением модификатора
const. Обычно это указатель. Операция
формирует результат указанного типа.
6
• Необходимость введения этой операции
обусловлена тем, что программист,
реализующий функцию, не обязан описывать
не изменяемые в ней формальные параметры
как const, хотя это и рекомендуется.
• Правила C++ запрещают передачу
константного указателя на место обычного.
Операция const_cast введена для того, чтобы
обойти это ограничение. Естественно,
функция не должна пытаться изменить
значение, на которое ссылается
передаваемый указатель, иначе результат
выполнения программы не определен.
7
• Пример:
• void print(int *p){// Функция не изменяет
//значение *p
•
cout << *p;}
• const int *p;
• /* print(&p); Ошибка, поскольку p объявлен как
указатель на константу */
• Операция const_cast используется в том случае,
когда программист уверен, что в теле функции
значение, на которое ссылается указатель, не
изменяется. Естественно, если есть возможность
добавить к описанию формального параметра
модификатор const, это предпочтительнее
использования преобразования типа при вызове
8
функции.
Операция static_cast
• Операция static_cast используется для
преобразования типа на этапе компиляции
между:
• целыми типами;
• целыми и вещественными типами;
• целыми и перечисляемыми типами;
• указателями и ссылками на объекты одной
иерархии, при условии, что оно однозначно и
не связано с понижающим преобразованием
виртуального базового класса.
9
• Формат операции:
• static_cast <тип> (выражение)
• Результат операции имеет указанный тип,
который может быть ссылкой, указателем,
арифметическим или перечисляемым типом.
• При выполнении операции внутреннее
представление данных может быть
модифицировано, хотя численное значение
остается неизменным. Например:
• float f = 100;
• int i = static_cast <int> (f);
// Целые и вещественные имеют различное
внутреннее представление
10
• Такого рода преобразования применяются обычно
для подавления сообщений компилятора о
возможной потере данных в том случае, когда есть
уверенность, что требуется выполнить именно это
действие. Результат преобразования остается на
совести программиста.
• Операция static_cast позволяет выполнять
преобразования из производного класса в базовый
и наоборот без ограничений:
• class B{};
• class C: public B{};
• C c;
• B *bp = static_cast<B*>(c);
// Производный ->
//базовый
• B b;
• C &cp = static_cast<C&>(b);
// Базовый ->
//производный 11
• Преобразование выполняется при компиляции,
при этом объекты могут не быть
полиморфными. Программист должен сам
отслеживать допустимость дальнейших
действий с преобразованными величинами.
• В общем случае использование для
преобразования указателей родственных
классов иерархии предпочтительнее
использовать операцию dynamic_cast. В этом
случае если преобразование возможно на этапе
компиляции, генерируется тот же код, что и для
static_cast.
• Кроме того, dynamic_cast допускает
перекрестное преобразование, нисходящее
приведение виртуального базового класса и
производит проверку допустимости приведения
во время выполнения.
12
Операция reinterpret_cast
• Операция reinterpret_cast применяется для
преобразования не связанных между собой
типов, например, указателей в целые или
наоборот, а также указателей типа void* в
конкретный тип. При этом внутреннее
представление данных остается неизменным,
а изменяется только точка зрения
компилятора на данные.
• Формат операции:
• reinterpret_cast <тип> (выражение)
• Результат операции имеет указанный тип,
который может быть ссылкой, указателем,
целым или вещественным типом.
13
• Пример:
char *p = reinterpret_cast <char*>(malloc(100));
long l = reinterpret_cast <long>(p);
• Различие между static_cast и reinterpret_cast
позволяет компилятору производить
минимальную проверку при использовании
static_cast, а программисту — обозначать
опасные преобразования с помощью
reinterpret_cast.
• Результат преобразования остается на
совести программиста.
14
Операция dynamic_cast
• Операция применяется для преобразования
указателей родственных классов иерархии, в
основном — указателя базового класса в
указатель на производный класс, при этом во
время выполнения программы производится
проверка допустимости преобразования.
• Формат операции:
• dynamic_cast <тип *> (выражение)
15
• Выражение должно быть указателем или
ссылкой на класс, тип — базовым или
производным для этого класса. После
проверки допустимости преобразования в
случае успешного выполнения операция
формирует результат заданного типа, в
противном случае для указателя результат
равен нулю (Если выражение равно нулю,
результат также равен нулю), а для ссылки
порождается исключение bad_cast. Если
заданный тип и тип выражения не относятся к
одной иерархии, преобразование не
допускается.
16
• Преобразование из базового класса в
производный называют понижающим
(downcast), так как графически в иерархии
наследования принято изображать
производные классы ниже базовых.
Приведение из производного класса в
базовый называют повышающим (upcast), а
приведение между производными классами
одного базового или, наоборот, между
базовыми классами одного производного —
перекрестным (crosscast).
17
Повышающее преобразование
• Выполнение с помощью операции
dynamic_cast повышающего
преобразования равносильно простому
присваиванию:
class B{ /* */ };
class C: public B{ /* */ };
C* c = new C;
B* b = dynamic_cast<B*>(c);
// Эквивалентно B* b = с;
18
Понижающее преобразование
• Чаще всего операция dynamic_cast применяется при
понижающем преобразовании — когда компилятор не
имеет возможности проверить правильность приведения.
• Производные классы могут содержать функции, которых
нет в базовых классах. Для их вызова через указатель
базового класса нужно иметь уверенность, что этот
указатель в действительности ссылается на объект
производного класса. Такая проверка производится в
момент выполнения приведения типа с использованием
RTTI (run-time type information) — «информации о типе во
время выполнения программы». Для того чтобы проверка
допустимости могла быть выполнена, аргумент операции
dynamic_cast должен быть полиморфного типа, то есть
иметь хотя бы один виртуальный метод.
19
• Для полиморфного объекта реализация
операции dynamic_cast весьма эффективна,
поскольку ссылка на информацию о типе
объекта заносится в таблицу виртуальных
методов, и доступ к ней осуществляется
легко.
• С точки зрения логики требование, чтобы
объект был полиморфным, также оправдано:
ведь если класс не имеет виртуальных
методов, его нельзя безопасным образом
использовать, не зная точный тип указателя.
А если тип известен, использовать операцию
dynamic_cast нет необходимости.
20
• Результат применения операции
dynamic_cast к указателю всегда требуется
проверять явным образом. В приведенном
ниже примере описан полиморфный базовый
класс B и производный от него класс C, в
котором определена функция f2. Для того
чтобы вызывать ее из функции demo только в
случае, когда последней передается
указатель на объект производного класса,
используется операция dynamic_cast с
проверкой результата преобразования:
21
#include <iostream.h>
#include <typeinfo.h>
class B{
public: virtual void f1(){};
};
class C: public B{
public: void f2(){cout << "f2";};
};
Для использования RTTI необходимо подключить
к программе заголовочный файл <typeinfo>.
Кроме того, необходимо, чтобы был установлен
соответствующий режим компилятора.
22
void demo(B* p){
C* c = dynamic_cast<C*>(p);
if (c) c->f2();
else cout << "Передан не класс С";
}
int main(){
B* b = new B;
demo(b); // Выдается сообщение "Передан не
класс С"
C* c = new C;
demo(c); // Выдается сообщение "f2"
(правильно)
return 0;
}
23
• При использовании в этом примере вместо
dynamic_cast приведения типов в стиле С, например:
• C* c = (C*) p;
• проконтролировать допустимость операции
невозможно, и если указатель p на самом деле не
ссылается на объект класса C, это приведет к ошибке.
• Другим недостатком приведения в стиле С является
невозможность преобразования в производный
виртуального базового класса, это запрещено
синтаксически. С помощью операции dynamic_cast
такое преобразование возможно при условии, что
класс является полиморфным и преобразование
недвусмысленно.
24
• Рассмотрим пример, в котором выполняется
понижающее преобразование виртуального
базового класса:
•
•
•
•
•
•
•
#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{};
//
//
A
/ \
// B C
// \ /
// D
25
void demo(A *a){
D* d = dynamic_cast<D*>(a);
if (d) { }
}
int main(){
D *d = new D; demo(d);
return 0;
}
26
Преобразование ссылок
• Для аргумента-ссылки смысл операции
преобразования несколько иной, чем
для указателя. Поскольку ссылка всегда
указывает на конкретный объект,
операция dynamic_cast должна
выполнять преобразование именно к
типу этого объекта. Корректность
приведения проверяется
автоматически, в случае несовпадения
порождается исключение bad_cast:
27
#include <iostream.h>
#include <typeinfo.h>
class B{
public: virtual void f1(){};
};
class C: public B{
public: void f2(){ };
};
28
void demo(B& p){
try{
C& c = dynamic_cast<C&>(p);
c.f2();
}
catch(bad_cast){
}
}
int main(){
B* b = new B; demo(*b);
C* c = new C; demo(*c);
return 0;
// Порождается
исключение
// Правильно
}
29
Перекрестное преобразование
• Операция dynamic_cast позволяет выполнять
безопасное преобразование типа между
производными классами одного базового класса,
например:
#include <iostream.h>
#include <typeinfo.h>
class B{
public: virtual void f1(){};
};
class C: public B{
public: void f2(){ };
};
30
class D: public B{ };
void demo(D* p){
C* c = dynamic_cast<C*>(p);
if(c)c->f2();
else cout <<" не выполнено ";
}
int main(){
B* b = new C; demo((D*)b);
return 0;
}
Классы C и D являются производными от класса
B. Функции demo передается указатель на класс
D, являющийся на самом деле указателем на
«братский» для него класс С, поэтому
динамическое преобразование типа из D в C в31
функции demo завершается успешно.
• При необходимости можно осуществить
преобразование между базовыми классами
одного производного класса, например:
#include <iostream.h>
#include <typeinfo.h>
class B{
public: virtual void f1(){ };
// B C
};
// \ /
class C{
// D
public: virtual void f2(){ };
};
32
class D: public B, public C{};
void demo(B* b){
C* c = dynamic_cast<C*>(b);
if(c)c->f2();
}
int main(){
D* d = new D; demo(d);
return 0;
}
33
• Класс D является потомком B и C,
поэтому содержит методы обоих
классов. Если в функцию demo
передается на самом деле указатель не
на B, а на D, его можно преобразовать к
его второму базовому классу C.
34
Динамическое определение типа
• Механизм идентификации типа во время
выполнения программы (RTTI) позволяет
определять, на какой тип в текущий момент
времени ссылается указатель, а также
сравнивать типы объектов. Для доступа к
RTTI в стандарт языка введена операция
typeid и класс type_info.
• Формат операции typeid:
• typeid (тип)
• typeid (выражение)
35
• Операция принимает в качестве параметра
имя типа или выражение и возвращает
ссылку на объект класса type_info,
содержащий информацию о типе. Если
операция не может определить тип операнда,
порождается исключение bad_typeid.
• Когда операнд представляет собой указатель
или ссылку на полиморфный тип, результатом
является динамическая информация о типе
(то есть объект type_info содержит
информацию о типе объекта, на который в
данный момент ссылается указатель).
36
• Если операндом является выражение,
представляющее собой ссылку на неполиморфный тип, объект type_info содержит
информацию о типе выражения, а не о типе
объекта, на который оно ссылается.
• Операцию typeid можно использовать как с
основными, так и с производными типами
данных.
• Класс type_info описан в заголовочном файле
<typeinfo> следующим образом:
37
class type_info{
public:
virtual ~type_info();
bool operator==(const type_info& rhs) const;
bool operator!=(const type_info& rhs) const;
bool before(const type_info& rhs) const;
const char* name() const;
private:
type_info(const type_info& rhs);
type_info& operator=(const type_info& rhs);
};
38
• Метод name возвращает указатель на строку,
представляющую имя типа, описываемого объектом
типа type_info.
• Виртуальный деструктор делает класс type_info
полиморфным.
• Конструктор копирования и операция присваивания
объявлены как private, чтобы исключить возможность
случайного копирования и присваивания объектов
класса.
• Операции == и != позволяют сравнивать два объекта
на равенство и неравенство, а функция before
выполняет побуквенное сравнение имен двух типов.
Для сравнения используется конструкция вида:
39
• typeid(T1).before(typeid(T2))
• Если имя типа Т1 лексикографически предшествует
имени Т2, результат будет истинным.
• Точная информация о типе объекта во время
выполнения программы может потребоваться,
например, когда программист расширяет
функциональность некоторого библиотечного базового
класса с помощью производного, и невозможно или
бессмысленно добавлять к базовому классу
виртуальные функции. Например:
#include <typeinfo.h>
class B{
public: virtual ~B(){};
};
40
class C: public B{
public: virtual void some_method(){ };
};
void demo(B* p){
if (typeid(*p) == typeid(C))
dynamic_cast<C*>(p)->some_method();
}
int main(){
C* c = new C; demo(c);
return 0;
}
41
• Информацию о типе полезно использовать и
в диагностических целях:
void print_type(some_obj *p){
cout << typeid(*p).name();
}
• Операция typeid не должна применяться
вместо виртуальных функций и в случаях,
когда тип объекта можно определить на этапе
компиляции.
42
Download