Динамическая идентификация типа ООП

Содержание

Слайд 2

ООП Для идентификации типа объекта используется оператор typeid, определенный в заголовке

ООП

Для идентификации типа объекта используется оператор typeid, определенный в заголовке

#include .
Его формат: typeid (object)
Здесь, параметр object является объектом, тип которого мы хотим идентифицировать. Он может иметь любой тип, в том числе встроенный или определенный пользователем.
Оператор typeid возвращает ссылку на объект типа type_infо, описывающий тип объекта. Стандарт "ISO/IEC 14882:2003(Е), Programming languages - С++" определяет класс type_infо так:

Динамическая идентификация типа

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);
};

Как видно, объекты типа type_info невозможно ни создать, ни скопировать – конструкторы и операция присваивания закрыты.
Объекты типа type_info можно сравнивать между собой, используя перегружен-ные операторы "==" и "!=", – и это основные операции, которые используются сов-местно с оператором typeid(); функция name() возвращает указатель на имя заданного типа.

Метод before() позволяет сортировать информацию о типе type_info. Нет никакой связи между отношениями упорядочения, определяемыми before(), и отношениями наследования. Если вызывающий объект предшествует объекту, использованному как параметр, функция before() возвращает значение true.

Слайд 3

ООП #include #include using namespace std; class myclass1 { /* ...

ООП

#include
#include
using namespace std;
class myclass1 { /* ... */

};
class myclass2 { /* … */ };
int main()
{ int i, j; float f; char *p;
myclass1 ob1; myclass2 ob2;
cout <<"Тип объекта i: "<< typeid(i).name();
cout << endl;
cout <<"Тип объекта f: "<< typeid(f).name();
cout << endl;
cout <<"Тип объекта p: "<< typeid(p).name();
cout << endl;
cout <<"Тип объекта ob1: "<< typeid(ob1).name();
cout << endl;
cout <<"Тип объекта ob2: "<< typeid(ob2).name();
cout << "\n\n";
if(typeid(i) == typeid(j))
cout <<"Типы объектов i и j совпадают\n";
if(typeid(i) != typeid(f))
cout <<"Типы объектов i и f не совпадают\n";
if(typeid(ob1) != typeid(ob2))
cout <<"ob1 и ob2 имеют разные типы\n";
return 0; }

Динамическая идентификация типа

Результаты работы этой программы приведены ниже.
Тип объекта i: int
Тип объекта f: float
Тип объекта р: char *
Тип объекта оb1: class myclass1
Тип объекта ob2: class myclass2
Типы объектов i и j совпадают
Типы объектов i и f не совпадают
Объекты оb1 и оb2 имеют разные типы

Пример использования typeid

Слайд 4

ООП #include #include using namespace std; class Mammal {public: //класс Mammal

ООП

#include
#include
using namespace std;
class Mammal {public: //класс Mammal –

полиморфный
virtual bool lays_eggs() { return false; }
// ... };
class Cat: public Mammal { public: /* ... */ };
class Platypus: public Mammal { public:
bool lays_eggs() { return true; } /* ... */ };
int main()
{ Mammal *p, AnyMammal;
Cat cat;
Platypus platypus;
p = &AnyMammal;
cout << "Указатель p ссылается на объект типа ";
cout << typeid(*p).name() << endl;
p = &cat;
cout << "Указатель p ссылается на объект типа ";
cout << typeid(*p).name() << endl;
p = &platypus;
cout << "Указатель p ссылается на объект типа ";
cout << typeid(*p).name() << endl;
return 0;
}

Динамическая идентификация типа

Самое важное свойство оператора typeid проявляется, когда он применяется к указателю на объект базового класса. В этом случае он автоматически возвращает тип реального объекта, на который ссылается указатель, причем этот объект может быть экземпляром как базового, так и производного классов. (Напомним, что указатель на объект базового класса может ссылаться на объекты производного класса.)
Т.о, в ходе выполнения программы, используя оператор typeid, можно идентифицировать тип объекта, на который ссылается указатель базового класса.
Результаты работы этой программы приведены ниже.
Указатель р ссылается на объект типа class Mammal,
Указатель р ссылается на объект типа class Cat,
Указатель р ссылается на объект типа class Platypus.

Пример применения typeid к иерархии полиморфных классов

Слайд 5

ООП Динамическая идентификация типа Если оператор typeid применяется к указателю на

ООП

Динамическая идентификация типа

Если оператор typeid применяется к указателю на объект полиморфного

базового класса, тип объекта, на который ссылается указатель, можно определить в ходе выполнения программы.
В любом случае, если оператор typeid применяется к указателю на объект неполиморфного базового класса, возвращается базовый тип указателя. Иначе говоря, невозможно определить, на объект какого типа ссылается этот указатель на самом деле.
Закомментируем, например, ключевое слово virtual, стоящее перед именем функции lays_eggs() в классе Mammal, перекомпилируем программу и запустим ее вновь:
Указатель р ссылается на объект типа class Mammal
Указатель р ссылается на объект типа class Mammal
Указатель р ссылается на объект типа class Mammal
Класс Mammal больше не является полиморфным, и каждый объект теперь имеет тип Mammal, т.е. тип указателя р.
Поскольку оператор typeid обычно применяется к разыменованным указателям, следует предусмотреть обработку исключительной ситуации bad_typeid, которая может возникнуть, если указатель будет нулевым.
Оператор typeid имеет вторую форму, получающую в качестве аргумента имя типа.
Ее общий вид приведен ниже:
typeid(<имя_типа>)
Например, следующий оператор совершенно правилен: cout << typeid(int).name();
Данная форма оператора typeid позволяет получить объект класса type_info, описывающий заданный тип. Благодаря этому ее можно применять в операторах сравнения типов.
Например: void WhatMammal(Mammal &ob)
{ cout << "Параметр ob ссылается на объект типа ";
cout << typeid(ob).name() << endl;
if(typeid(ob) == typeid(Cat))
cout << "Кошки боятся воды.\n";
}

Разбор примера применения typeid к иерархии полиморфных классов

Слайд 6

ООП #include using namespace std; class Mammal { public: virtual bool

ООП

#include
using namespace std;
class Mammal { public:
virtual bool lays_eggs()

{return false;}
// ... };
class Cat: public Mammal { public: /* … /* };
class Platypus: public Mammal { public:
bool lays_eggs() { return true; } /* ... */ };
class Dog: public Mammal { public: /* … /* };
// Фабрика объектов, производных от класса Mammal
Mammal *factory()
{ switch(rand() % 3 ) { case 0: return new Dog;
сase 1: return new Cat; case 2: return new Platypus; }
return 0; }
int main()
{ Mammal *ptr; // Указатель на базовый класс
int i; int c=0, d=0, p=0;
// Создаем и подсчитываем объекты
for(i=0; i<10; i++) // Создаем и подсчитываем объекты
{ ptr = factory(); // Создаем объект
cout<<"Тип объекта: "<< typeid(*ptr).name();
cout<< endl;
// Подсчёт
if(typeid(*ptr) == typeid(Dog)) d++;
if(typeid(*ptr) == typeid(Cat)) c++;
if(typeid(*ptr) == typeid(Platypus)) p++; }
cout << endl;
cout << " Созданные животные:\n";
cout << " Собаки: " << d << endl;
cout << " Кошки: " << c << endl;
cout << " Утконосы: " << p << endl;
return 0;
}

Динамическая идентификация типа

Функция factory() создает объекты различных классов, производных от класса Mammal. (Функции, создающие объекты, иногда называются фабриками объектов (object factory)). Конкретный тип создаваемого объекта задается функцией rand(), которая представляет собой генератор случайных чисел в языке С++. Т.е., тип создаваемого объекта заранее не известен. Программа создаст 10 объектов и подсчитывает количество объектов каждого типа. Поскольку все объекты генерируются функцией factory(), для идентификации фактического типа объекта используется оператор typeid.
Результаты работы этой программы приведены ниже.
Тип объекта: class Platypus
Тип объекта: class Platypus
Тип объекта: class Cat
Тип объекта: class Cat
Тип объекта: class Platypus
Тип объекта: class Cat
Тип объекта: class Dog
Тип объекта: class Dos
Тип объекта: class Cat
Тип объекта: class Platypus
Созданные животные:
Собаки: 2
Кошки: 4
Утконосы: 4

Применение динамической идентификации типа

Слайд 7

ООП #include using namespace std; template class myclass { T a;

ООП

#include
using namespace std;
template class myclass {
T

a;
public:
myclass(T i) { a = i; } // ...
};
int main()
{ myclass o1(10), o2(9);
myclass o3(7.2);
cout << "Тип объекта o1: ";
cout << typeid(o1).name() << endl;
cout << "Тип объекта o2: ";
cout << typeid(o2).name() << endl;
cout << "Тип объекта o3: ";
cout << typeid(o3).name() << endl;
cout << endl;
if(typeid(o1) == typeid(o2))
cout << "Объекты o1 и o2 имеют одинаковый тип\n";
if(typeid(o1) == typeid(o3))
cout << "Ошибка\n";
else
cout << "Объекты o1 и o3 имеют разные типы\n";
return 0;
}

Динамическая идентификация типа

Тип объекта, являющегося объектом шаблонного класса, определяется, в частности, тем, какие данные используются в качестве обобщенных при создании конкретного объекта. Если при создании двух экземпляров шаблонного класса используются данные разных типов, считается, что эти объекты имеют разные типы.
Результаты работы этой программы приведены ниже.
Тип объекта o1: class myclass
Тип объекта o2: class myclass
Тип объекта o3: class myclass
Объекты ol и о2 имеют одинаковый типы Объекты ol и оЗ имеют разные типы
Как видим, хотя два объекта представляют собой экземпляры одного и того же шаблонного класса, если параметры не совпадают, их типы считаются разными. В данной программе объект o1 имеет тип myclass, а объект оЗ –myclass. Таким образом, их типы не совпадают.

Применение оператора typeid к шаблонным классам

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

Слайд 8

ООП В языке С++ существуют пять операторов приведения типов. Первый оператор

ООП

В языке С++ существуют пять операторов приведения типов. Первый оператор

является вполне традиционным и унаследован от языка С.
Операция приведения типов в стиле С:
Она может записываться в двух формах:
тип (выражение)
(тип) выражение
Результатом операции является значение заданного типа, например:
int а = 2;
float b = 6.8;
printf ("%lf %d", double (a), (int) b);
Величина а преобразуется к типу double, а переменная b – к типу int с отсечением дробной части, в обоих случаях внутренняя форма представления результата операции преобразования иная, чем форма исходного значения.
Остальные четыре оператора приведения типов были добавлены впоследствии, в С++.
К ним относятся операторы: dynamic_cast,
const_cast,
reinterpret_cast,
static_cast.
Эти операторы позволяют полнее контролировать процессы приведения типов.

Операторы приведения типа

Слайд 9

ООП Оператор dynamic_cast Операторы приведения типа Оператор dynamic_cast осуществляет динамическое приведение

ООП

Оператор dynamic_cast

Операторы приведения типа

Оператор dynamic_cast осуществляет динамическое приведение типа с

последующей проверкой корректности приведения. Если приведение оказалось некорректным, оно не выполняется.
Общий вид оператора dynamic_cast:
dynamic_cast (expr);
Здесь - параметр target_type задает результирующий тип,
- параметр ехрr – выражение, которое приводится к новому типу.
Результирующий тип должен быть указательным или ссылочным, а приводимое выражение – вычислять указатель или ссылку. Таким образом, оператор dynamic_cast можно применять для приведения типов указателей или ссылок.
Оператор dynamic_cast предназначен для приведения полиморфных типов. Dynamic_cast применяется для приведения типов в иерархической структуре наследования; он может приводить базовый тип к производному, производный к базовому или один производный тип – к другому производному типу. Допустим, даны два полиморфных класса B и D, причем класс D является производным от класса B. Тогда оператор dynamic_cast может привести указатель типа D* к типу B*. Это возможно благодаря тому, что указатель на объект базового класса может ссылаться на объект производного класса. Однако обратное динамическое приведение указателя типа B* к типу D* возможно лишь в том случае, если указатель действительно ссылается на объект класса D.
Оператор dynamic_cast достигает цели, если указатель или ссылка, подлежавшие приведению, ссылаются на объект результирующего класса или объект класса, производного от результирующего. В противном случае приведение типов считается неудавшимся. В случае неудачи оператор dynamic_cast, примененный к указателям, возвращает нулевой указатель. Если оператор dynamic_cast применяется к ссылкам, в случае ошибки генерируется исключительная ситуация bad_cast.
Слайд 10

ООП Оператор dynamic_cast Операторы приведения типа Пример. Класс Base является полиморфным,

ООП

Оператор dynamic_cast

Операторы приведения типа

Пример.
Класс Base является полиморфным, a Derived

– производным от него:
Base *bp, b_ob;
Derived *dp, d_ob;
bp = &d_ob; // указатель базового типа ссылается на объект производного класса
dp=dynamic_cast(bp); //приведение указателя производного типа выполнено успешно
if(dp) cout << "Приведение выполнено успешно";
Приведение указателя bр, имеющего базовый тип, к указателю dp, имеющему производный тип, выполняется успешно, поскольку указатель bp на самом деле ссылается на объект класса Derived. Т.о, этот фрагмент выводит на экран сообщение "Приведение выполнено успешно".
Однако в следующем фрагменте приведение не выполняется, потому что указатель bр ссылается на объект класса Base, а приведение базового объекта к производному невозможно:
bp = &b_ob; // указатель базового типа ссылается на объект класса Base
dp = dynamic_cast (bp); // Ошибка
if(!dp) cout << "Приведение не выполняется";
Поскольку приведение невозможно, фрагмент выводит на экран сообщение "Приведение не выполняется".
Слайд 11

ООП Оператор dynamic_cast Операторы приведения типа Пример. Программа демонстрирует разные ситуации

ООП

Оператор dynamic_cast

Операторы приведения типа

Пример. Программа демонстрирует разные ситуации применения оператора

dynamic_cast

#include
using namespace std;
class Base { public:
virtual void f() {cout<<"Внутри класса Base\n";} /* ... */ };
class Derived : public Base { public:
void f() {cout<< Внутри класса Derived \n"; } };
int main()
{ Base *bp, b_ob; Derived *dp, d_ob;
dp = dynamic_cast (&d_ob);
if(dp) {
cout << "Приведение типа Derived * к типу Derived * выполнено успешно\n";
dp->f(); } else cout << "Ошибка\n";
cout << endl;
bp = dynamic_cast (&d_ob);
if(bp) { cout << "Приведение типа Derived * к типу Base * выполнено успешно\n";
bp->f(); } else cout << "Ошибка\n";
cout << endl;
bp = dynamic_cast (&b_ob);
if(bp) { сout << "Приведение типа Base * к типу Base * выполнено успешно\n";
bp->f(); } else cout << "Ошибка\n";
cout << endl;
dp = dynamic_cast (&b_ob);
// см. продолжение

if(dp) // продолжение
cout << "Ошибка\n"; else
cout <<"Приведение типа Base * к типу Derived * невозможно\n";
сout << endl;
bp = &d_ob; // Указатель bp ссылается на объект
класса Derived
dp = dynamic_cast (bp);
if(dp) {
сout<<"Приведение указателя bp к типу Derived * выполнено успешно поскольку bp действительно ссылается на объект класса Derived\n";
dp->f(); } else cout << "Ошибка\n";
cout << endl;
bp=&b_ob;//Указатель bp ссылается на объект класса Base
dp = dynamic_cast (bp);
if(dp) cout << "Error"; else
{cout<<"Теперь приведение указателя bp к типу Derived * невозможно, так как указатель bp на самом деле ссылается на объект класса Base\n"; }
cout << endl;
dp = &d_ob; // Указатель dp ссылается на объект класса Derived
bp = dynamic_cast (dp);
if(bp) {
cout<<"Приведение указателя dp к типу Base * выполнено успешно\n"; bp->f(); }
else cout << "Error\n";
return 0;
}

Слайд 12

ООП Оператор dynamic_cast Операторы приведения типа Пример. Программа демонстрирует разные ситуации

ООП

Оператор dynamic_cast

Операторы приведения типа

Пример. Программа демонстрирует разные ситуации применения оператора

dynamic_cast

Результаты работы этой программы приведены ниже:
Приведение типа Derived * к типу Derived * выполнено успешно.
Внутри класса Derived
Приведение типа Derived * к типу Base * выполнено успешно.
Внутри класса Derived
Приведение типа Base * к типу Base * выполнено успешно.
Внутри класса Base
Приведение типа Base * к типу Derived * невозможно
Приведение указателя bp к классу Derived * выполнено успешно, поскольку указатель bp
действительно ссылается на объект класса Derived
Внутри класса Derived
Теперь приведение указателя bp к типу Derived невозможно, так как
указатель bp на самом деле ссылается на объект класса Base.
Приведение указателя dp к классу Base * выполнено успешно.
Внутри класса Derived

Слайд 13

ООП Замена оператора typeid оператором dynamic_cast Операторы приведения типа Иногда оператор

ООП

Замена оператора typeid оператором dynamic_cast

Операторы приведения типа

Иногда оператор dynamic_cast можно

использовать вместо оператора typeid.
Допустим, что класс Base является полиморфным, a Derived ‒ производным от него. В следующем фрагменте указателю dp присваивается адрес объекта, на который ссылается указатель bр, только если этот объект действительно является экземпляром класса Derived:
Base *bp;
Derived *dp;
// ...
if(typeid(*bp) == typeid(Derived)) dp = (Derived *) bp;
В этом фрагменте используется традиционный оператор приведения. Это вполне безопасно, поскольку оператор if проверяет легальность приведения с помощью оператора typeid. Однако в этой ситуации лучше заменить оператор typeid и условный оператор if оператором dynamic_cast:
dp = dynamic_cast (bp);
Поскольку оператор dynamic_cast выполняется успешно, только если приводимый объект является либо экземпляром результирующего класса, либо объектом класса, производного от результирующего, указатель dp содержит либо нулевой указатель, либо адрес объектов типа Derived.
Поскольку оператор dynamic_cast выполняется успешно, только если приведение является легальным, его применение иногда упрощает ситуацию.
Слайд 14

ООП Оператор dynamic_cast Операторы приведения типа Пример. В этой программе одна

ООП

Оператор dynamic_cast

Операторы приведения типа

Пример. В этой программе одна и та

же операция выполняется дважды: сначала ‒ с помощью оператора typeid, а затем ‒ оператора dynamic_cast

#include
#include
using namespace std;
class Base { public: virtual void f() {} };
class Derived : public Base { public:
void derivedOnly()
{ cout << "Объект класса Derived \n"; } };
int main()
{ Base *bp, b_ob;
Derived *dp, d_ob;
// ************************************
// Применение оператора typeid
// ************************************
bp = &b_ob;
if(typeid(*bp) == typeid(Derived))
{ dp = (Derived *) bp; dp->derivedOnly(); }
else cout << "Приведение типа Base к типу
Derived невозможно\n";
bp = &d_ob;
if(typeid(*bp) == typeid(Derived))
{ dp = (Derived *) bp; dp->derivedOnly(); }
else
cout << "Ошибка, приведение невозможно!\n";
// см. продолжение

// продолжение
// ************************************
// Применение оператора dynamic_cast
// ************************************
bp = &b_ob;
dp = dynamic_cast (bp);
if(dp) dp->derivedOnly();
else cout << "Приведение типа Base к типу
Derived невозможно\n";
bp = &d_ob;
dp = dynamic_cast (bp);
if(dp) dp->derivedOnly();
else
cout<<"Ошибка, приведение невозможно!\n";
return 0;
}
Как видим, оператор dynamic_cast упрощает логику приведения указателя базового типа к указателю производного типа.
Приведение типа Base к типу Derived невозможно.
Объект класса Derived.
Приведение типа Base к типу Derived невозможно.
Объект класса Derived.

Слайд 15

ООП Применение dynamic_cast к шаблонным классам Операторы приведения типа #include using

ООП

Применение dynamic_cast к шаблонным классам

Операторы приведения типа

#include
using namespace std;
template

class Num { protected: T val;
public: Num(T x) { val = x; }
virtual T getval() { return val; } /* ... */ };
template class SqrNum : public Num
{ public: SqrNum(T x) : Num(x) { }
T getval() { return val * val; } };
int main()
{ Num *bp, numInt_ob(2);
SqrNum *dp, sqrInt_ob(3);
Num numDouble_ob(3.3);
bp = dynamic_cast *> (&sqrInt_ob);
if(bp) { cout << "Приведение типа SqrNum* к
типу Num*\n"<< " выполнено успешно";
cout<<"Значение равно "<getval()< else cout << "Ошибка\n"; cout << endl;
dp = dynamic_cast *> (&numInt_ob);
if(dp) cout << "Ошибка\n"; else { cout<<"Приве-дение типа Num* к типу SqrNum* невозможно\n";
cout<<"Приведение указателя на объект базового \n"; cout<<" класса к указателю на объект производного типа невозможно\n"; } cout << endl;
bp = dynamic_cast *> (&numDouble_ob);
if(bp) cout << "Ошибка\n"; else cout<<"Приведе-ние типа Num* к типу Num* невоз-можно\n"; cout<<"Это два разных типа\n";
return 0;
}

Результаты работы этой программы:
Приведение типа SqrNum* к типу Num* выполнено успешно
Значение равно 9
Приведение типа Num* к типу SqrNum* невозможно
Приведение указателя на объект базового класса к указателю на объект производного класса невозможно.
Приведение типа Num* к типу Num* невозможно.
Это два разных типа.
Основной смысл этого фрагмента заключается в том, что с помощью оператора dynamic_cast нельзя привести указатель на объект одной шаблонной специализации к указателю на экземпляр другой шаблонной специализации.
Напоминание: точный тип объекта шаблонного класса определяется типом данных, которые используются при его создании. Таким образом, типы Num и Num различаются.

Слайд 16

ООП Оператор const_cast Операторы приведения типа Оператор const_cast используется для явного

ООП

Оператор const_cast

Операторы приведения типа

Оператор const_cast используется для явного замещения модификаторов

const и/или volatile. Операция служит для удаления модификатора const. Как правило, она используется при передаче в функцию константного указателя на место формального параметра, не имеющего модификатора const. Результирующий тип должен совпадать с исходным, за исключением атрибутов const и volatile.
Общий вид оператора const_cast:
const_cast (expr);
Здесь - параметр type задает результирующий тип приведения,
- параметр ехрr – выражение, которое приводится к новому типу.

#include
using namespace std;
void sqrval(const int *val) { int *p;
// Удаление модификатора const
p = const_cast (val);
*p = *val * *val; }
int main()
{ int x = 10;
cout<<"Значение x перед вызовом: "< sqrval(&x);
cout<<"Значение х после вызова: "< return 0;
}

Эта программа выводит на экран следующие результаты:
Значение х перед вызовом: 10
Значение х после вызова: 100
Как видим, функция sqrval() изменяет значение переменной х, даже если ее параметр является константным указателем.
Применение оператора const_cast для удаления атрибута const небезопасно. Его следует использовать осторожно.
Атрибут const можно удалить только с помощью оператора const_cast.
Операторы dynamic_cast, static_cast и reinterpret_cast на атрибут const не влияют.

Слайд 17

ООП Оператор static_cast Операторы приведения типа Оператор static_cast выполняет неполиморфное приведение.

ООП

Оператор static_cast

Операторы приведения типа

Оператор static_cast выполняет неполиморфное приведение. Его можно

применять для любого стандартного преобразования типов. Проверка приведения в ходе выполнения программы не производится.
Оператор static_cast имеет следующий вид:
static_cast (expr)
Здесь - параметр type задает результирующий тип приведения,
- параметр ехрr – выражение, которое приводится к новому типу.
Оператор static_cast, по существу, заменяет исходный оператор приведения. Просто он выполняет неполиморфное приведение.
Например, следующая программа приводит переменную типа int к типу double:

#include
using namespace std;
int main()
{
int i;
for(i=0; i<10; i++)
cout << static_cast (i) / 3 << " ";
return 0;
}