Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ООП (С++) Лекции Бобин.doc
Скачиваний:
61
Добавлен:
08.02.2015
Размер:
625.66 Кб
Скачать

Глава 11. Динамическая идентификация типа (rtti). Операторы приведения типа.

п.11.1. Оператор typeid

Механизм RTTI(Run-TimeTypeIdentification) позволяет во время выполнения программы определить фактический (динамический) тип объекта, доступ к которому осуществляется через указатель или ссылку. Для этого используется ключевое словоtypeid. Внешне применение оператораtypeidвыглядит как вызов функции с одним параметром:

typeid(выражение);

Оператор typeidвозвращает ссылку на константу типаtype_info, которая описывает тип выражения, переданного в качестве параметра. В классеtype_infoопределены операторы сравнения(operator == (),operator != ()) и методname(), который возвращает строку в стиле Си, содержащую полное имя типа. Все остальные компоненты класса (в том числе конструкторы и оператор присваивания) закрыты. Поэтому невозможно создать переменную типаtype_info и сохранить в ней результатtypeid. Операторtypeidприменим как к встроенным типам данных, так и к пользовательским классам. Классtype_info определен в заголовочном файлеtypeinfo.

Пример:

#define PRINT(x) cout << “Тип “ #x “: “ << typeid(x).name() << endl

#include <typeinfo>

#include <iostream>

using std::cout;

using std::endl;

class MyClass

{

};

struct MyStruct

{

};

int main()

{

int iVal;

float fVal;

char str[80];

MyClass mc;

MeStruct ms;

PRINT(iVal);

PRINT(fVal);

PRINT(str);

PRINT(mc);

PRINT(ms);

cout << “Типы ms и mc”;

if(typeid(ms) == typeid(mc))

cout << “ совпадают”;

else

cout << “ не совпадают”;

cout << endl;

return 0;

}

Не во всех компиляторах печатаются слова «class» и «struct».

Для указателя или ссылки на объект не полиморфного класса оператор typeidвыдаст информацию о его статическом (заявленном) типе.

Пример:

class Mammal

{

};

class Dog: public Mammal

{

};

class Cat: public Mammal

{

};

int main()

{

Mammal Cheburashka;

Cat Matroskin;

Dog Sharik;

Mammal *p;

p = &Cheburashka;

cout << typeid(*p).name() << endl; // class Mammal

p = &Matroskin;

cout << typeid(*p).name() << endl; // class Mammal

p = &Sharik;

cout << typeid(*p).name() << endl; // class Mammal

cout << typeid(p).name() << endl; // -> class *Mammal

return 0;

}

Для указателя или ссылки на объект полиморфного класса оператор typeidвыдаст информацию о его динамическом (реальном типе).

Пример:

class Mammal

{

public:

virtual ~Mammal()

{

}

};

class Dog: public Mammal

{

};

class Cat: public Mammal

{

};

int main()

{

Mammal Cheburashka;

Cat Matroskin;

Dog Sharik;

Mammal *p;

p = &Cheburashka;

cout << typeid(*p).name() << endl; // class Mammal

p = &Matroskin;

cout << typeid(*p).name() << endl; // class Cat

p = &Sharik;

cout << typeid(*p).name() << endl; // class Dog

cout << typeid(p).name() << endl;

return 0;

}

Оператор typeidнельзя применять к нулевым или бестиповым указателям. Если так сделать, то программа упадет.

Пример:

class Mammal

{

public:

virtual ~Mammal()

{

}

};

class Dog: public Mammal

{

};

class Cat: public Mammal

{

};

Mammal * Create()

{

if(rand()%2 == 1)

return new Dog;

else

return new Cat;

}

int main()

{

Mammal *p;

int cats = 0;

int dogs = 0;

for(int i = 0; i < 100; ++i)

{

p = Create();

if(typeid(*p) == typeid(Cat))

++cats;

else

++dogs;

delete p;

}

cout << “Создано“ << dogs << “псов” << endl;

cout << “Создано “ << cats << “ котов” << endl;

return 0;

}

п.11.2. Приведение типа в С++

В С++ существует 5 конструкций для инициирования действий по приведению типа:

  1. классический оператор приведения в стиле Си ((тип)переменная;)

  2. оператор dynamic_cast

  3. оператор static_cast

  4. оператор const_cast

  5. оператор reinterpret_cast

Последние 4 называются операторами приведения в стиле С++. Все они имеют единый синтаксис:

оператор <желаемый_тип>(выражение)

п.11.2.1. Оператор static_cast

Выполняет обычное не полиморфное приведение типа. Если речь идет о типах, не связанных иерархией, вызывается перегруженный оператор приведения.

Пример:

class Base

{

};

class Derived: public Base

{

};

int main()

{

double dVal = 5.3;

int iVal;

Base b;

Derived d;

Base *pb = &b;

Base *pd = &d;

iVal = static_cast<int>(dVal);

dVal = static_cast<double>(b); // !

// Предполагается вызов Base::operator int(), а такого нет, следовательно,

// ошибка времени компиляции

Derived *perror = static_cast<Derived *>(pb); // 1) !

Derived *pok = static_cast<Derived *>(pd); // 2)

return 0;

}

  1. Произойдет ошибка: при попытке обратиться к компоненту, появившемуся в производном классе произойдет ошибка, поскольку указатель *perrorреально смотрит на объект базового класса, в котором данного компонента просто нет.

  2. Хотя *pdявляется указателем на объект базового класса, реально он настроен на объект производного класса. Такое преобразование допустимо.

В первом случае нужно:

if(typeid(*pb) == typeid(Derived))

Derived *perror = static_cast<Derived *>(pb);

else

Derived *perror = NULL;

Следует отметить, что оба класса являются не полиморфными, следовательно, perror = NULLвсегда.

п.11.2.2. Оператор dynamic_cast. Понижающее приведение типа

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

Понижающее приведение типа не всегда выполнимо и не всегда безопасно.

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

Понижающее приведение типа реализуется с помощью оператора dynamic_cast. Этот оператор производит полиморфное приведение типа с проверкой корректности такого приведения. Операторdynamic_cast успешно выполняет заданное приведение типа, только если указатель или ссылка, подлежащие приведению, настроены на объект желаемого типа. Или на объект типа производного от желаемого. В противном случае, операторdynamic_cast, применяемый к указателям, возвращает нулевой указатель. Если оператор применяется к ссылкам, то происходит ошибка времени выполнения (в некоторых компиляторах возвращается нулевое значение типаint). Если речь идет не о полиморфном преобразовании или преобразовании не связанных типов, операторdynamic_cast ведет себя какstatic_cast.

Пример:

pd = dynamic_cast<Derived *>(pb);

Эквивалентная запись:

if(typeid(*pb) == typeid(Derived))

pd = static_cast<Derived *>(pb);

else

pd = NULL;

Пример (если использовать dynamic_cast вместо static_cast):

void func(Base *pb)

{

Derived *pd1 = dynamic_cast<Derived *>(pb); // (1)

Derived *pd2 = static_cast<Derived *>(pb); // (2)

pd1->setx(6); // !

pd2->setX(6); // !

}

class Derived: public Base

{

int x;

public:

void setX(int newX)

{

x = newX;

}

};

Структура класса Baseв данном примере роли не играет. МетодsetX()не является перекрытием какого-либо из классаBase. КлассыBaseиDerivedявляются полиморфными!

Пусть где-нибудь создается объект класса Derived, который передается на вход функцииfunc().

Derived d;

func(&d); // Derived * -> Base *

В момент вызова func()происходит повышающее приведение типа указателя на объект производного класса. У наблюдателя, который находится внутриfunc() есть указатель*pb на объект базового класса. Внутриfunc() возникла необходимость обратиться кsetX() производного класса. В данном примере необходимо преобразование*pb типу «указатель на объект производного класса». Сделать можно двумя способами:

  1. С помощью dynamic_cast

  2. С помощьюstatic_cast

В данном случае разницы нет, функция отработает корректно.

Рассмотрим другую ситуацию: Пусть у класса Basеесть второй потомок, не содержащий методаsetX()и поляx. Создадим объект второго производного класса, который также передадим на вход нашей функцииfunc().

Base

NULL

Derived

setX()

Derived2

Derived2 d;

func (&d);

Произойдет такое же повышающее приведение типа. При таком вызове вариант с оператором dynamic_cast (1) выдастNULL, и программа упадет при попытке разыменовать нулевой указатель.

В строчке (2) произошло бы преобразование типа, причем корректно (с точка зрения машины). Однако в последней строчке поведение программы не предсказуемо, так как объект, переданный в func()не имеет ни методаsetX(), ни изменяемого им поляx.

Вывод: Были рассмотрены 2 случая:

  1. Поведение func()при передаче ей объекта классаDerived. Результат – функция работает корректно, оба способа понижающего приведения типа работают корректно.

  2. Поведение func()при передаче ей объекта классаDerived2(представитель параллельной иерархии классов-потомков классаBase). Результат:

  1. программа успешно скомпилируется;

  2. понижающее приведение типа с помощью dynamic_cast завершится ошибкой (вариантNULL), которую можно отследить далее в теле функции, и программа упадет в методеsetX()(this->x = ...гдеthis– нулевой указатель);

  3. понижающее приведение типа с помощью static_cast произойдет успешно (так как с формальной точки зрения оно допустимо), но это преобразование приведет к непредсказуемым последствиям в последней строчке функции. Отследить эту логическую ошибку в тексте программы уже невозможно.

Понижающее приведение типа следует выполнять только с помощью оператора dynamic_cast с последующей проверкой успеха.

п.11.2.3. Оператор const_cast

Позволяет управлять модификаторами constиvolatile. При использовании данного оператора желаемый тип объекта должен в точности совпадать с заявленным, за исключением модификаторовconstиvolatile.

Пример:

const int -> int // +

const int -> double // -

Снять константность можно только с помощью оператора static_cast.

п.11.2.4. Оператор reinterpret_cast

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

Оператор reinterpret_castявляется самым опасным в С++, поэтому использовать его надо крайне аккуратно.

Оператор reinterpret_cast хорошо использовать для приведения указателей.

Пример(разработка оболочки, инкапсулирующей файловые операции):

#include <cstdio>

class File

{

FILE *fd;

File(const File &); // Запрет на копирование и присваивание

File & operator = (const File &);

public:

File();

File(const char *name, const char *mode);

~File();

bool open(const char *name, const char *mode);

void close();

void write(const char *data, unsigned size);

void read(char *data, unsigned size);

void print(int val);

void print(double val);

void print(const char *str);

void scan(int &val);

void scan(double &val);

void scan(char *str);

};

File::File()

{

fd = NULL;

}

File::File(const char *name, const char *mode): fd(NULL)

{

open(name, mode);

}

File::~File()

{

close();

}

boold File::open(const char *name, const char *mode)

{

close();

fd = fopen(name, mode);

return fd != NULL;

}

void File::close()

{

if(fd != NULL)

{

fclose(fd);

fd = NULL;

}

}

void File::write(const char *data, unsigned size)

{

if(fd != NULL)

fwrite(fd, 1, size, data);

}

void File::read(char *data, unsigned size)

{

if(fd != NULL)

fread(fd, 1, size, data);

}

void File::print(int val)

{

if(fd != NULL)

fprintf(fd, “%d”, val);

}

void File::print(double val)

{

if(fd != NULL)

fprintf(fd, “%f”, val);

}

void File::print(const char *str)

{

if(fd != NULL)

fprintf(fd, “%s”, str);

}

void File::scan(int &val)

{

if(fd != NULL)

fscanf(fd, “%d”, &val);

}

void File::scan(double &val)

{

if(fd != NULL)

fscanf(fd, “%f”, &val);

}

void File::scan(char *str)

{

if(fd != NULL)

fscanf(fd, “%s”, val);

}

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

  1. автоматическое закрытие файла при выходе из блока;

  2. перед выполнением операций чтения, записи осуществляется проверка ее целесообразности;

Рекомендуется добавить метод проверки готовности файла к работе.

Замечание: При использовании методовread() иwrite() целесообразно использовать операторreinterpret_cast:

f.write(reinterpret_cast<char *>(&v), sizeof(v));

где v– произвольная переменная.