Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих (Стенли Липпман) 3-е хххх.pdf
Скачиваний:
86
Добавлен:
30.05.2015
Размер:
5.92 Mб
Скачать

С++ для начинающих

987

19.1.2. Оператор typeid

Второй оператор, входящий в состав RTTI, – это typeid, который позволяет выяснить фактический тип выражения. Если оно принадлежит типу класса и этот класс содержит хотя бы одну виртуальную функцию-член, то ответ может и не совпадать с типом самого выражения. Так, если выражение является ссылкой на базовый класс, то typeid

#include <typeinfo>

programmer pobj; employee &re = pobj;

//с функцией name() мы познакомимся в подразделе, посвященном type_info

//она возвращает C-строку "programmer"

сообщает тип производного класса объекта: coiut << typeid( re ).name() << endl;

Операнд re оператора typeid имеет тип employee. Но так как re это ссылка на тип класса с виртуальными функциями, то typeid говорит, что тип адресуемого объекта programmer (а не employee, на который ссылается re). Программа, использующая такой оператор, должна включать заголовочный файл <typeinfo>, что мы и сделали в этом примере.

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

для получения списка его свойств во время сеанса работы с отладчиком или для правильного сохранения или извлечения объекта из базы данных. Оператор typeid допустимо использовать с выражениями и именами любых типов. Например, его операндами могут быть выражения встроенных типов и константы. Если операнд не

int iobj;

cout << typeid( iobj ).name() << endl; // печатается: int

принадлежит к типу класса, то typeid просто возвращает его тип:

cout << typeid( 8.16 ).name() << endl; // печатается: double

Если операнд имеет тип класса, в котором нет виртуальных функций, то typeid

class Base { /* нет виртуальных функций */ };

class Derived : public Base { /* нет виртуальных функций */ };

Derived dobj;

Base *pb = &dobj;

возвращает тип операнда, а не связанного с ним объекта:

cout << typeid( *pb ).name() << endl; // печатается: Base

С++ для начинающих

988

Операнд typeid имеет тип Base, т.е. тип выражения *pb. Поскольку в классе Base нет виртуальных функций, результатом typeid будет Base, хотя объект, на который указывает pb, имеет тип Derived.

#include <typeinfo>

 

employee *pe = new manager;

 

employee& re = *pe;

// истинно

if ( typeid( pe ) == typeid( employee* ) )

// что-то сделать

 

/*

// ложно

if ( typeid( pe ) == typeid( manager* ) )

if ( typeid( pe ) == typeid( employee ) )

// ложно

if ( typeid( pe ) == typeid( manager ) )

// ложно

Результаты, возвращенные оператором typeid, можно сравнивать. Например:

*/

Условие в инструкции if сравнивает результаты применения typeid к операнду, являющемуся выражением, и к операнду, являющемуся именем типа. Обратите внимание, что сравнение

typeid( pe ) == typeid( employee* )

// вызов виртуальной функции

возвращает истину. Это удивит пользователей, привыкших писать: pe->salary();

что приводит к вызову виртуальной функции salary() из производного класса manager. Поведение typeid(pe) не подчиняется данному механизму. Это связано с тем, что pe указатель, а для получения типа производного класса операндом typeid должен быть тип класса с виртуальными функциями. Выражение typeid(pe) возвращает тип pe, т.е. указатель на employee. Это значение совпадает со значением typeid(employee*), тогда как все остальные сравнения дают ложь.

Только при употреблении выражения *pe в качестве операнда typeid результат будет

typeid( *pe ) == typeid( manager )

// истинно

содержать тип объекта, на который указывает pe: typeid( *pe ) == typeid( employee ) // ложно

В этих сравнениях *pe выражение типа класса, который имеет виртуальные функции,

поэтому результатом применения typeid будет тип адресуемого операндом объекта manager.

Такой оператор можно использовать и со ссылками:

С++ для начинающих

989

 

typeid( re ) == typeid( manager )

// истинно

 

 

typeid( re ) == typeid( employee )

// ложно

 

typeid( &re ) == typeid( employee* )

// истинно

 

typeid( &re ) == typeid( manager* )

// ложно

 

 

 

 

В первых двух сравнениях операнд re имеет тип класса с виртуальными функциями, поэтому результат применения typeid содержит тип объекта, на который ссылается re. В последних двух сравнениях операнд &re имеет тип указателя, следовательно, результатом будет тип самого операнда, т.е. employee*.

На самом деле оператор typeid возвращает объект класса типа type_info, который определен в заголовочном файле <typeinfo>. Интерфейс этого класса показывает, что можно делать с результатом, возвращенным typeid. (В следующем подразделе мы подробно рассмотрим этот интерфейс.)

19.1.3. Класс type_info

Точное определение класса type_info зависит от реализации, но некоторые его

class type_info {

// представление зависит от реализации private:

type_info( const type_info& );

type_info& operator= ( const type_info& ); public:

virtual ~type_info();

int operator==( const type_info& ); int operator!=( const type_info& );

const char * name() const;

характерные черты остаются неизменными в любой программе на C++:

};

Поскольку копирующие конструктор и оператор присваивания закрытые члены класса

#include <typeinfo>

type_info t1; // ошибка: нет конструктора по умолчанию // ошибка: копирующий конструктор закрыт

type_info, то пользователь не может создать его объекты в своей программе: type_info t2 (typeid( unsigned int ) );

Единственный способ создать объект класса type_info воспользоваться оператором typeid.

В классе определены также операторы сравнения. Они позволяют сравнивать два объекта type_info, а следовательно, и результаты, возвращенные двумя операторами typeid. (Мы говорили об этом в предыдущем подразделе.)

С++ для начинающих

990

 

typeid( re )

== typeid( manager )

// истинно

 

 

typeid( *pe )

!= typeid( employee )

// ложно

 

 

 

 

 

Функция name() возвращает C-строку с именем типа, представленного объектом

#include <typeinfo> int main() {

employee *pe = new manager;

// печатает: "manager"

cout << typeid( *pe ).name() << endl;

type_info. Этой функцией можно пользоваться в программах следующим образом:

}

Для работы с функцией-членом name() нужно включить заголовочный файл

<typeinfo>.

Имя типа это единственная информация, которая гарантированно возвращается всеми реализациями C++, при этом используется функция-член name() класса type_info. В начале этого раздела упоминалось, что поддержка RTTI зависит от реализации и иногда в классе type_info бывают дополнительные функции-члены. Чтобы узнать, каким образом обеспечивается поддержка RTTI в вашем компиляторе, обратитесь к справочному руководству по нему. Кроме того, можно получить любую информацию, которую компилятор знает о типе, например:

список функций-членов класса;

способ размещения объекта в памяти, т.е. взаимное расположение подобъектов базового и производных классов.

Одним из способов расширения поддержки RTTI является включение дополнительной информации в класс, производный от type_info. Поскольку в классе type_info есть виртуальный деструктор, то оператор dynamic_cast позволяет выяснить, имеется ли некоторое конкретное расширение RTTI. Предположим, что некоторый компилятор предоставляет расширенную поддержку RTTI посредством класса extended_type_info, производного от type_info. С помощью оператора dynamic_cast программа может узнать, принадлежит ли объект типа type_info, возвращенный оператором typeid, к типу extended_type_info. Если да, то пользоваться расширенной поддержкой RTTI разрешено.

С++ для начинающих

991

#include <typeinfo>

// Файл typeinfo содержит определение типа extended_type_info

void func( employee* p )

{

// понижающее приведение типа type_info* к extended_type_info* if ( eti *eti_p = dynamic_cast<eti *>( &typeid( *p ) ) )

{

//если dynamic_cast завершается успешно,

//можно пользоваться информацией из extended_type_info через eti_p

}

else

{

//если dynamic_cast завершается неудачно,

//можно пользоваться только стандартным type_info

}

}

Если dynamic_cast завершается успешно, то оператор typeid вернет объект класса extended_type_info, т.е. компилятор обеспечивает расширенную поддержку RTTI, чем программа может воспользоваться. В противном случае допустимы только базовые средства RTTI.

Упражнение 19.1

Дана иерархия классов, в которой у каждого класса есть конструктор по умолчанию и

class X { ... }; class A { ... };

class B : public A { ... }; class C : public B { ... };

виртуальный деструктор:

class D : public X, public C { ... };

(a) D *pd = new D;

Какие из данных операторов dynamic_cast завершатся неудачно?

(b)A *pa = new C;

A *pa = dynamic_cast< A* > ( pd );

(c)B *pb = new B;

C *pc = dynamic_cast< C* > ( pa ); D *pd = dynamic_cast< D* > ( pb );

С++ для начинающих

992

(d)A *pa = new D;

X *px = dynamic_cast< X* > ( pa );

Упражнение 19.2

Объясните, когда нужно пользоваться оператором dynamic_cast вместо виртуальной функции?

Упражнение 19.3

Пользуясь иерархией классов из упражнения 19.1, перепишите следующий фрагмент так,

чтобы в нем использовался ссылочный вариант dynamic_cast для преобразования *pa в

if ( D *pd = dynamic_cast< D* >( pa ) ) { // использовать члены D

}

else {

// использовать члены A

тип D&:

}

Упражнение 19.4 Дана иерархия классов, в которой у каждого класса есть конструктор по умолчанию и

class X { ... }; class A { ... };

class B : public A { ... }; class C : public B { ... };

виртуальный деструктор:

class D : public X, public C { ... };

(a)A *pa = new D;

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

(b)X *px = new D;

cout << typeid( *px ).name() << endl;

(c)C obj;

A& ra = cobj;

cout << typeid( &ra ).name() << endl;

(d)X *px = new D; A& ra = *px;

Какое имя типа будет напечатано в каждом из следующих случаев: cout << typeid( ra ).name() << endl;