Использующая полиморфизм и информацию о типе



Времени выполнения с нисходящими приведениями

Типа, dynamic_cast, typeid и type_info

        

    В текущий расчетный период наша воображаемая компания решила премировать служащих BasePlusCommissionEmployee, увеличив на 10% их базовый оклад. Обрабатывая в примере 5 объекты Employee полиморфно, мы не беспокоились об их «частностях». Теперь, однако, чтобы модифицировать базовые оклады для BasePlusCommissionEmployee, мы должны идентифицировать тип каждого служащего во время исполнения и затем действовать соответственно. Этот раздел демонстрирует мощные возможности, предоставляемые информацией о типе времени выполнения (RTTI) и нисходящими приведениями типа, которые позволяют программе определять тип объекта во время исполнения и обращаться с объектом соответствующим образом.

Пример 2

Программа на рис. 13.25 использует разработанную иерархию Employee и увеличивает на 10% базовый оклад каждого из служащих BasePlusCommissionEmployee. Строка 31 объявляет четырехэлементный вектор employees, в котором хранятся указатели на объекты Employee. Строки 34-41 заполняют вектор адресами динамически выделенных объектов классов SalariedEmployee, HourlyEmployee, и BasePlusCommissionEmployee.

    Оператор for в строках 44-66 проходит по вектору employees и выводит информацию о каждом служащем, вызывая элемент-функцию print (строка 46). Поскольку, как вы помните, print объявлена в базовом классе Employee виртуальной, система активирует соответствующую функцию print объекта производного класса.

    В этом примере мы хотим, встречая объекты BasePlusCommissionEmployee, увеличивать их базовый оклад на 10 процентов. Так как мы обрабатываем служащих обобщенным образом (т.е. полиморфно), мы не можем (используя то, чему научились) иметь уверенность относительно типа объекта Employee, с которым работаем в каждый момент времени. Это создает проблему, поскольку мы должны идентифицировать объекты BasePlusCommissionEmployee, если они нам встречаются, чтобы они могли получить 10-процентную прибавку к окладу. Чтобы это осуществить, мы используем операцию dynamic_cast (строка 51), которая позволяет нам определить, относится ли объект к типу BasePlusCommissionEmployee. Это операция нисходящего приведения. Строки 50-52 динамически приводят employees[ i ] от типа Employee * к типу BasePlusCommissionEmployee *. Если элемент вектора указывает на объект, который является объектом BasePlusCommissionEmployee, то адрес этого объекта присваивается указателю derivedPtr. В противном случае derivedPtr присваивается 0.

    Если значение, возвращаемое операцией dynamic_cast в строках 50-52, не 0, то это объект нужного типа и оператор if в строках 56-63 производит специальную обработку, требуемую объектом BasePlusCommissionEmployee. Строки 58, 60 и 62 вызывают функции getBaseSalary и setBaseSalary для извлечения и обновления оклада служащего.

    Строка 65 вызывает функцию earnings для объекта, на который указывает employees[ i ]. Так как функция earnings объявлена в базовом классе виртуальной, программа вызывает функцию earnings объекта производного класса — еще один пример динамического связывания.

    Цикл for в строках 69-76 выводит тип каждого объекта Employee и использует операцию delete для освобождения динамической памяти, на которую указывает каждый из элементов вектора. Операция typeid (строка 73) возвращает ссылку на объект класса typeinfo, который содержит информацию о типе операнда операции, включая имя этого типа. Вызов элемент-функции name класса type_info (строка 73) возвращает строку-указатель с именем типа (например, "class BasePlusCominissionEmployee") аргумента, переданного typeid. [Замечание. Точное содержимое возвращаемой элемент-функцией name строки может меняться от компилятора к компилятору.] Чтобы использовать typeid, программа должна включать заголовочный файл <typeinfo> (строка 16).

    Заметьте, что в этом примере мы, приводя указатель Employee к типу BasePlusCominissionEmployee (строки 50-52), избегаем нескольких ошибок компиляции. Если мы удалим из строки 51 dynamic_east и попытаемся присвоить текущий указатель Employee непосредственно указателю derivedPtr, то получим сообщение об ошибке компиляции. C++ не позволяет программе присваивать указатель базового класса указателю производного класса, поскольку здесь не имеет места отношение является — Employee не является BasePlusCominissionEmployee. Отношение является приложимо только в направлении от производного класса к базовому, но не наоборот.

    Аналогично, если бы мы в строках 58, 60 и 62 использовали для вызова функций производного класса getBaseSalary и setBaseSalary не указатель производного класса derivedPtr, а текущий указатель базового класса из employees, то получили бы сообщения об ошибках компиляции в каждой из этих строк. Как вы знаете из прошлого занятия, недопустима попытка вызова функции, имеющейся только в производном классе, через указатель базового класса. Хотя строки 58, 60 и 62 исполняются только в случае, когда 

derivedPtr не 0 (т.е. приведение может быть произведено), мы не можем пытаться вызывать функции getBaseSalary и setBaseSalary класса BasePlusCominissionEmployee через указатель базового класса Employee. Через указатель базового класса Employee можно вызывать только функции, имеющиеся в базовом классе, т.е. earnings, print, get- и sef-функции класса Employee.

 

Виртуальные деструкторы

    При полиморфной обработке динамически распределенных объектов классовой иерархии может возникать одна проблема. Пока вы встречались только с невиртуальными деструкторами, т.е. теми, что объявлялись без ключевого слова virtual. Если объект производного класса с невиртуальным деструктором уничтожается явным применением операции delete к указателю базового класса, стандарт C++ специфицирует, что в этом случае поведение не определено.

Пример 3

    Простым решением этой проблемы является создание в базовом классе виртуального деструктора (т.е. деструктора, объявленного с ключевым словом virtual). Это делает виртуальными деструкторы всех производных классов, несмотря на то, что их имена отличаются от имени деструктора базового класса. Теперь, если объект иерархии уничтожается явным образом применением операции delete к указателю базового класса, будет вызван деструктор соответствующего класса в зависимости от объекта, на который этот указатель ссылается. Как вы помните, при уничтожении объекта производного класса уничтожается и базовая часть объекта производного класса, поэтому важно, чтобы исполнялись деструкторы и производного, и базового классов. Деструктор базового класса исполняется автоматически после исполнения деструктора

производного класса.

    Если в классе имеются виртуальные функции, предусмотрите для него виртуальный деструктор, даже если он и не требуется классу. Классы, производные от него, могут содержать деструкторы, которые должны вызываться должным образом.

    Конструкторы не могут быть виртуальными. Объявление конструктора виртуальным приводит к ошибке компиляции.


Дата добавления: 2018-08-06; просмотров: 243; Мы поможем в написании вашей работы!

Поделиться с друзьями:






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