Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Class D: public B, public C, public V

Поиск

{

public:

D(int ia, int ib, int ic, int iq, int iv):

V(iv), Q(iq), B(iq,iv,ia,ib), C(iq,iv,ia,ic)

// вызов V(iv) инициализирует объект виртуальной базы V

{}

};

Интересная особенность характерна для конструктора класса D. Так как для класса D класс V является базовым дважды, необходимо дважды вызывать конструктор V::V(int). Но это запрещено синтаксисом С++. Выход из этой ситуации таков. Конструктор V::V(int) инициализирует объект виртуального базового класса V, а объект невиртуального базового класса V инициализируется конструктором по умолчанию V::V().


2.5. Преобразование динамических типов.
Динамическая идентификация типов

Благодаря полиморфизму в С++ возможна инициализация указателей и ссылок на базовые классы соответственно указателями и ссылками на объекты производных классов. В результате появляется возможность работать с компонентами производных классов через указатели и ссылки на объекты базовых классов. Однако, чтобы обратиться к какому-либо компоненту, нужно точно знать текущий динамический тип указателя или ссылки (поскольку неизвестно, например, есть ли этот компонент в текущем динамическом типе).

Уточнение динамического типа обеспечивает операция dynamic_cast. Формат ее использования следующий:

dynamic_cast<new_type>(expression)

где expression – выражение, для которого требуется уточнить динамический тип; new_type – новый тип выражения.

В качестве new_type в описанном формате может выступать ссылка или указатель (возможно, с модификаторами) на полностью определенный полиморфный класс. Также new_type может иметь тип void *, const void *, volatile void * или const volatile void *. В свою очередь, expression может представлять собой указатель или ссылку на полностью определенный полиморфный класс или же быть нулевым указателем.

Работа операции dynamic_cast подчиняется следующим правилам.

1. Если new_type – это указатель на класс, то expression должно быть праводопустимым выражением типа «указатель на класс». Результат преобразования – значение выражения expression (праводопустимое выражение), приведенное к типу new_type.

2. Если new_type – это ссылка на класс, то expression должно быть леводопустимым выражением типа «ссылка на класс». Результат преобразования – значение выражения expression (леводопустимое выражение), приведенное к типу new_type.

3. При совпадении типов new_type и expression тип исходного выражения не меняется (если нет отличий в модификаторах; если new_type более квалифицирован модификаторами, то выполняется конвертация типа по модификаторам).

4. Если выражение expression есть нулевой указатель, то результатом преобразования будет нулевой указатель типа new_type.

Операция dynamic_cast позволяет преобразовывать динамический тип в двух направлениях: от производного класса к базовому и от базового к производному. Если, например, указатель pBase на базовый класс CBase инициализировать адресом объекта d производного класса CDerived, указатель pBase будет связан с частью объекта d, соответствующей классу CBase (динамический тип адреса объекта как бы обобщается до CBase*). Если затем указатель pBase преобразовать к типу CDerived*, то он свяжется с адресом объекта d (динамический тип указателя будет уточнен до CDerived*).

Операцию dynamic_cast можно использовать и в иерархиях с множественным наследованием. Причем, если преобразование выполняется в направлении от производного класса к базовому, то результатом является ссылка или указатель на один из объектов базового класса, входящих в объект производного класса (если объектов базового класса в объекте производного класса несколько, то выбирается только один из них).

В определенных случаях операция dynamic_cast способна генерировать ошибки. Например, при попытке привести типы в разных ветвях иерархии классов, не связанных наследованием, или в случае закрытого наследования (когда объект базового класса недоступен). Проявлением ошибки в случае указателей будет нулевое значение результирующего указателя; в случае ссылки генерируется исключение std::bad_cast. Очевидно, что преобразование указателей более опасно, чем преобразование ссылок, поскольку ошибка с указателями в момент возникновения никак себя не проявляет (многие программисты с большой неохотой проверяют указатели на NULL перед их разыменованием) и сказывается лишь в следующих подвыражениях или операторах.

Приведенный ниже пример иллюстрирует описанные особенности использования операции dynamic_cast:

Пример

#include <typeinfo.h> // необходимо для std::bad_cast

class A {

public:

virtual void fA() {}

// делаем все классы полиморфными

};

class B: public A {

public:

void fB() {}

};

class C: public B {

public:

void fC() {}

};

class D: private B {

public:

void fD() {}

};

// использование классов

A * p = 0;

C c;

D d;

p = dynamic_cast<A*>(&c); // обобщение динамического типа &c

p->fA(); // так можно вызвать только функции класса A

dynamic_cast<B*>(p)->fB(); // уточнение динамического типа p

dynamic_cast<C*>(p)->fC(); // можно вызвать и fB, и fC

// p = dynamic_cast<A*>(&d); // так как D закрыто наследует от B

// p->fA(); // преобразование завершается с ошибкой (p == NULL)

try {

*p = dynamic_cast<A&>(d); // здесь будет исключение

}

catch (std::bad_cast &) {

// обработка исключения

}

p->fA(); // это никогда не выполнится

...

В С++, наряду с преобразованием динамических типов имеется возможность выяснения типов выражений непосредственно при выполнении программы (run-time type identification – RTTI). Средства поддержки RTTI описаны в заголовочном файле typeinfo.h. Их основу составляет класс std::type_info, который инкапсулирует информацию о типе объекта и функции для работы с ней. В нем есть, например, функция name, возвращающая строку с именем текущего типа объекта. Имеется также функция before, позволяющая проверить лексикографическую упорядоченность имен двух типов.

Выяснение типа выражения реализуется унарной операцией typeid. Если операция typeid срабатывает без ошибок, то ее результатом является ссылка на константный статический объект класса std::type_info, содержащий информацию о типе операнда (на каждый тип получается уникальный объект type_info, так что сравнение типов можно заменить сравнением адресов этих объектов). Аргументом операции typeid может быть леводопустимое выражение (lvalue), дающее полиморфный класс, а также идентификатор типа; кроме того, аргументом может быть выражение неполиморфного типа. Если аргумент – lvalue полиморфного типа, то typeid определяет текущий динамический тип аргумента. Если же аргумент – идентификатор типа, то результатом является информация о данном типе. Наконец, если аргументом является выражение неполиморфного типа, то typeid определяет его статический тип. Операция typeid может завершиться с ошибкой (например, когда аргумент – разыменованный нулевой указатель); в этом случае выбрасывается исключение класса std::bad_typeid (также определен в файле typeinfo.h).

Приведенный ниже пример показывает различные варианты использования механизма RTTI.

Пример

#include <iostream> // для cout, endl

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

class A {

public:

virtual void fA() {}

};

class B: public A {

public:

void fB() {}

};

class C: public B {

public:

void fC() {}

};

class D: public B {

public:

void fD() {}

};

class E: public D {

public:

void fE() {}

};

int main() {

// объекты с различными модификаторами

C c;

const D d;

volatile E e;

A * pA = &c; // модификация динамического типа

std::cout << "typeid(*pA) = " << typeid(*pA).name() << std::endl

<< "typeid(c) = " << typeid(c).name() << std::endl

<< "typeid(pA) = " << typeid(pA).name() << std::endl;

// выведет:

// typeid(*pA) = C

// typeid(c) = C

// typeid(pA) = A *

pA = const_cast<D*>(&d); // модификация динамического типа

std::cout << "typeid(*pA) = " << typeid(*pA).name() << std::endl

<< "typeid(d) = " << typeid(d).name() << std::endl;

// выведет:

// typeid(*pA) = D

// typeid(d) = D

pA = const_cast<E*>(&e);

std::cout << "typeid(*pA) = " << typeid(*pA).name() << std::endl

<< "typeid(E) = " << typeid(E).name() << std::endl;

// выведет:

// typeid(*pA) = E

// typeid(e) = E

if (typeid(*pA) == typeid(E)) {

// условие есть true

std::cout << "typeid(*pA) < typeid(C) == "

<< (typeid(*pA).before(typeid(C))?

"true": "false") << std::endl;

// выведет: typeid(*pA) < typeid(C) == false

std::cout << "typeid(*pA) > typeid(C) == "

<< (typeid(C).before(typeid(*pA))?

"true": "false") << std::endl;

// выведет: typeid(*pA) > typeid(C) == true

}

return 0;

}

Работа приведенной программы очевидна из комментариев. Единственное, о чем следует сказать отдельно, – обработка модификаторов const, volatile, const volatile. Операция typeid, согласно стандарту, игнорирует любые модификаторы первого уровня. Например, const A и A, const B & и B она будет считать идентичными типами соответственно, а const A * и A * она будет различать (здесь const – модификатор второго уровня).

Рассмотренный в данной главе механизм наследования существенно расширяет возможности языка С++ с точки зрения формирования новых классов и отношений между ними. В то же время, существуют ситуации, когда наследование не дает достаточно гибких решений или вообще неприменимо ввиду концептуальных ограничений предметной области. В этих случаях более разумным может оказаться использование механизма дружественных функций и классов, который рассмотрен в следующей главе, а также механизма вложения, описанного в главе 4.

 


Вопросы для самопроверки

 

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

2. Какие формы наследования поддерживаются в С++?

3. Дайте общий формат определения производного класса.

4. Опишите структуру объекта производного класса.

5. В каком порядке вызываются конструкторы и деструкторы при создании и удалении объекта производного класса?

6. В каких случаях при построении производного класса целесообразно использование закрытого и защищенного наследования? Дайте обоснование и приведите пример.

7. Дан класс CEmployee, описывающий сотрудника некоторой фирмы, а также классы CChief и COrdinary, представляющие соответственно руководящих и рядовых сотрудников этой фирмы. Как наиболее эффективно объединить эти классы в иерархию? Приведите эскиз.

8. Дан класс CDate, описывающий даты, и класс CEmployee, представляющий сотрудников некоторой организации. Класс CEmployee должен содержать дату рождения сотрудника; для ее представления разумно использовать уже готовый класс CDate. Какую схему наследования необходимо применить, чтобы эффективно использовать функциональность класса CDate в классе CEmployee. Привести эскиз классовой иерархии и дать ее обоснование.

9. Дан класс CPerson, описывающий человека, и класс CEmployee, представляющий сотрудников некоторой организации. Ясно, что класс CEmployee будет содержать многое из того, что есть в CPerson, поэтому разумно связать эти два класса отношением наследования. Какую схему наследования необходимо применить, чтобы эффективно использовать функциональность класса CPerson в классе CEmployee. Привести эскиз классовой иерархии и дать ее обоснование.

10. Даны классы CArmor, CFighter, CBomber, CArtillery, представляющие вражеские цели в программе «виртуальная карта разведчика» (CArmor – танки, CFighter – истребители, CBomber – бомбардировщики, CArtillery – артиллерийские орудия). Как наиболее рационально связать эти классы в иерархию?

11. Что такое полиморфизм?

12. В чем отличие между статическим и виртуальным полиморфизмом? Приведите пример.

13. Какие классы принято называть полиморфными?

14. Приведите концептуальные отличия между виртуальными и невиртуальными компонентными функциями.

15. Почему деструктор базового класса обязательно должен быть виртуальным? Опишите возможные последствия его «невиртуальности» на примере.

16. В каком случае приведенное ниже определение деструктора базового класса X следует считать правильным и безопасным: ~X() { delete[] __data; }? Поясните ответ.

17. Пусть в некотором классе CSomeClass есть компонентная функция SomeFunc. Класс CSomeClass предполагается использовать в качестве базового в классовой иерархии. По каким признакам можно определить, следует ли делать функцию SomeFunc виртуальной?

18. Класс CMatrix содержит функцию virtual void Input(); Производный от него класс CSpecialMatrix включает функцию virtual void Input(istream &); К чему приводит несоответствие сигнатур функции Input в базовом и производном классах?

19. Требуется спроектировать классы, представляющие прямоугольники, квадраты, эллипсы, окружности и треугольники. Как наиболее рационально построить иерархию из этих классов? Приведите эскизы определения классов.

20. Какие классы называют абстрактными? Приведите пример определения абстрактного класса.

21. Как поступить, если проектируемый класс надо сделать абстрактным, но функций, которые можно было бы объявить чисто виртуальными, нет?

22. При каком условии возможно создание объекта абстрактного класса?

23. Опишите концептуальные отличия между виртуальными и чисто виртуальными функциями.

24. Имеется класс, описывающий интерфейсные окна графической системы. Выделите те аспекты поведения этого класса, которые позволяют сделать его абстрактным.

25. Что понимается под статическим и динамическим типами выражения?

26. Как можно получить название текущего динамического типа выражения? Приведите фрагмент кода.

27. Каким образом можно сравнить динамические типы двух выражений?

28. Класс C определен в виде: class C: A, B { /* */ }; Определения классов A, B, в свою очередь, имеют вид: class A: W { /* */ }; class B: W { /* */ }; (здесь W – еще один класс). Правильно ли определен конструктор класса C, если известно, что во всех классах A, B, C, W определены конструкторы по умолчанию: C::C(): A(), B() {} Будет ли это определение верным при виртуальном базовом классе W?

29. Опишите сущность проблемы доминирования виртуальных функций при множественном наследовании.

30. Каковы особенности применения операции dynamic_cast в иерархиях классов с множественным наследованием?

31. Можно ли один и тот же базовый класс использовать и как виртуальный, и как невиртуальный? Приведите пример такой иерархии классов.

32. Каким образом можно сделать функцию виртуальной по отношению к двум динамическим типам одновременно? Ответ поясните примером.

 

Задачи

 

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

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

3. Придумать иерархию классов, которая бы наиболее рационально представляла животных в виртуальном справочнике любителя животных. Необходимо представить только классы млекопитающих, птиц и рыб. Для моделирования общих свойств этих классов можно использовать абстрактные базовые классы.

4. Предложить иерархию классов, описывающую цели для пункта управления огнем дивизионной артиллерии. Требуется представить следующие классы целей: бронированные машины (БТР, танки); стационарные артиллерийские орудия (гаубицы, системы залпового огня); пункты сосредоточения личного состава (штабы, казармы). Для описания общих свойств классов можно применить абстрактные базовые классы.

5. Разработать фрагмент иерархии классов для компьютерной игры "Подземелье гоблинов". Суть игры проста: в подземном лабиринте, где есть клад, живут гоблины. Задача героя - найти клад. Но гоблины не только помогают герою в поисках клада, но и мешают. Разработке подлежат классы: гоблин; злой гоблин (мешает герою в поисках клада); добрый гоблин (помогает герою найти клад); гоблин-хамелеон (это гоблин, который может быть как злым, так и добрым в зависимости от обстоятельств).

6. Разработать часть классовой иерархии для описания объектов системы автоматического управления. В число классов включаются: объект управления, робот, технологическая ячейка, роботизированная технологическая ячейка.

 




Поделиться:


Последнее изменение этой страницы: 2016-08-14; просмотров: 186; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.145.99.221 (0.008 с.)