19.1.1. Оператор dynamic_cast

19.1.1. Оператор dynamic_cast

Оператор dynamic_cast можно применять для преобразования указателя, ссылающегося на объект типа класса в указатель на тип класса из той же иерархии. Его также используют для трансформации l-значения объекта типа класса в ссылку на тип класса из той же иерархии. Приведение типов с помощью оператора dynamic_cast, в отличие от других имеющихся в C++ способов, осуществляется во время выполнения программы. Если указатель или l-значение не могут быть преобразованы в целевой тип, то dynamic_cast завершается неудачно. В случае приведения типа указателя признаком неудачи служит возврат нулевого значения. Если же l-значение нельзя трансформировать в ссылочный тип, возбуждается исключение. Ниже мы приведем примеры неудачного выполнения этого оператора.

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

class employee {

public:

virtual int salary();

};

class manager : public employee {

public:

int salary();

};

class programmer : public employee {

public:

int salary();

};

void company::payroll( employee *pe ) {

// используется pe-salary()

}

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

Допустим, класс employee перестал удовлетворять нашим потребностям, и мы хотим его модифицировать, добавив еще одну функцию-член bonus(), используемую совместно с salary() при расчете платежной ведомости. Для этого нужно включить новую функцию-член в классы, составляющие иерархию employee:

class employee {

public:

virtual int salary(); // ca?ieaoa

virtual int bonus(); // i?aiey

};

class manager : public employee {

public:

int salary();

};

class programmer : public employee {

public:

int salary();

int bonus();

};

void company::payroll( employee *pe ) {

// eniieucoaony pe-salary() e pe-bonus()

}

Если параметр pe функции payroll() указывает на объект типа manager, то вызывается виртуальная функция-член bonus() из базового класса employee, поскольку в классе manager она не замещена. Если же pe указывает на объект типа programmer, то вызывается виртуальная функция-член bonus() из класса programmer.

После добавления новых виртуальных функций в иерархию классов придется перекомпилировать все функции-члены. Добавить bonus() можно, если у нас есть доступ к исходным текстам функций-членов в классах employee, manager и programmer. Однако если иерархия была получена от независимого поставщика, то не исключено, что в нашем распоряжении имеются только заголовочные файлы, описывающие интерфейс библиотечных классов и объектные файлы с их реализацией, а исходные тексты функций-членов недоступны. В таком случае перекомпиляция всей иерархии невозможна.

Если мы хотим расширить функциональность библиотеки классов, не добавляя новые виртуальные функции-члены, можно воспользоваться оператором dynamic_cast.

Этот оператор применяется для получения указателя на производный класс, чтобы иметь возможность работать с теми его элементами, которые по-другому не доступны. Предположим, что мы расширяем библиотеку за счет добавления новой функции-члена bonus() в класс programmer. Ее объявление можно включить в определение programmer, находящееся в заголовочном файле, а саму функцию определить в одном из своих исходных файлов:

class employee {

public:

virtual int salary();

};

class manager : public employee {

public:

int salary();

};

class programmer : public employee {

public:

int salary();

int bonus();

};

Напомним, что payroll() принимает в качестве параметра указатель на базовый класс employee. Мы можем применить оператор dynamic_cast для получения указателя на производный programmer и воспользоваться им для вызова функции-члена bonus():

void company::payroll( employee *pe )

{

programmer *pm = dynamic_cast programmer* ( pe );

// anee pe oeacuaaao ia iauaeo oeia programmer,

// oi dynamic_cast auiieieony oniaoii e pm aoaao

// oeacuaaou ia ia?aei iauaeoa programmer

if ( pm ) {

// eniieuciaaou pm aey auciaa programmer::bonus()

}

// anee pe ia oeacuaaao ia iauaeo oeia programmer,

// oi dynamic_cast auiieieony iaoaa?ii

// e pm aoaao niaa??aou 0

else {

// eniieuciaaou ooieoee-?eaiu eeanna employee

}

}

Оператор

dynamic_cast

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

Таким образом, оператор dynamic_cast осуществляет сразу две операции. Он проверяет, выполнимо ли запрошенное приведение, и если это так, выполняет его. Проверка производится во время работы программы. dynamic_cast безопаснее, чем другие операции приведения типов в C++, поскольку проверяет возможность корректного преобразования.

Если в предыдущем примере pe действительно указывает на объект типа programmer, то операция dynamic_cast завершится успешно и pm будет инициализирован указателем на объект типа programmer. В противном случае pm получит значение 0. Проверив значение pm, функция company::payroll() может узнать, указывает ли pm на объект programmer. Если это так, то она вызывает функцию-член programmer::bonus() для вычисления премии программисту. Если же dynamic_cast завершается неудачно, то pe указывает на объект типа manager, а значит, необходимо применить более общий алгоритм расчета, не использующий новую функцию-член programmer::bonus().

Оператор dynamic_cast употребляется для безопасного приведения указателя на базовый класс к указателю на производный. Такую операцию часто называют понижающим приведением (downcasting). Она применяется, когда необходимо воспользоваться особенностями производного класса, отсутствующими в базовом. Манипулирование объектами производного класса с помощью указателей на базовый обычно происходит автоматически, с помощью виртуальных функций. Однако иногда использовать виртуальные функции невозможно. В таких ситуациях dynamic_cast предлагает альтернативное решение, хотя этот механизм в большей степени подвержен ошибкам, чем виртуализация, и должен применяться с осторожностью.

Одна из возможных ошибок – это работа с результатом dynamic_cast без предварительной проверки на 0: нулевой указатель нельзя использовать для адресации объекта класса. Например:

void company::payroll( employee *pe )

{

programmer *pm = dynamic_cast( pe );

// iioaioeaeuiay ioeaea: pm eniieucoaony aac i?iaa?ee cia?aiey

static int variablePay = 0;

variablePay += pm-bonus();

// ...

}

Результат, возвращенный dynamic_cast, всегда следует проверять, прежде чем использовать в качестве указателя. Более правильное определение функции company::payroll() могло бы выглядеть так:

void company::payroll( employee *pe )

{

// auiieieou dynamic_cast e i?iaa?eou ?acoeuoao

if ( programmer *pm = dynamic_cast( pe ) ) {

// eniieuciaaou pm aey auciaa programmer::bonus()

}

else {

// eniieuciaaou ooieoee-?eaiu eeanna employee

}

}

Результат операции dynamic_cast используется для инициализации переменной pm внутри условного выражения в инструкции if. Это возможно, так как объявления в условиях возвращают значения. Ветвь, соответствующая истинности условия, выполняется, если pm не равно нулю: мы знаем, что операция dynamic_cast завершилась успешно и pe указывает на объект programmer. В противном случае результатом объявления будет 0 и выполняется ветвь else. Поскольку теперь оператор и проверка его результата находятся в одной инструкции программы, то невозможно случайно вставить какой-либо код между выполнением dynamic_cast и проверкой, так что pm будет использоваться только тогда, когда содержит правильный указатель.

В предыдущем примере операция dynamic_cast преобразует указатель на базовый класс в указатель на производный. Ее также можно применять для трансформации l-значения типа базового класса в ссылку на тип производного. Синтаксис такого использования dynamic_cast следующий:

dynamic_cast Type & &( lval )

где Type& – это целевой тип преобразования, а lval – l-значение типа базового класса. Операнд lval успешно приводится к типу Type& только в том случае, когда lval действительно относится к объекту класса, для которого один из производных имеет тип Type.

Поскольку нулевых ссылок не бывает (см. раздел 3.6), то проверить успешность выполнения операции путем сравнения результата (т.е. возвращенной оператором dynamic_cast ссылки) с нулем невозможно. Если вместо указателей используются ссылки, условие

if ( programmer *pm = dynamic_cast programmer* ( pe ) )

нельзя переписать в виде

if ( programmer &pm = dynamic_cast& programmer& &( pe ) )

Для извещения об ошибке в случае приведения к ссылочному типу оператор dynamic_cast возбуждает исключение. Следовательно, предыдущий пример можно записать так:

#include typeinfo

void company::payroll( employee &re )

{

try {

programmer &rm = dynamic_cast& programmer & &( re );

// eniieuciaaou rm aey auciaa programmer::bonus()

}

catch ( std::bad_cast ) {

// eniieuciaaou ooieoee-?eaiu eeanna employee

}

}

В случае неудачного завершения ссылочного варианта dynamic_cast возбуждается исключение типа bad_cast. Класс bad_cast определен в стандартной библиотеке; для ссылки на него необходимо включить в программу заголовочный файл . (Исключения из стандартной библиотеки мы будем рассматривать в следующем разделе.)

Когда следует употреблять ссылочный вариант dynamic_cast вместо указательного? Это зависит только от желания программиста. При его использовании игнорировать ошибку приведения типа и работать с результатом без проверки (как в указательном варианте) невозможно; с другой стороны, применение исключений увеличивает накладные расходы во время выполнения программы (см. главу 11).

Поделитесь на страничке

Следующая глава >

Похожие главы из других книг

9.4 Оператор While

Из книги C++ автора Хилл Мюррей

9.4 Оператор While Оператор while имеет видwhile ( выражение ) операторВыполнение подоператора повторяется, пока значение выржения остается ненулевым. Проверка выполняется перед каждым выполнением оператора. Выражение обрабатывается как в услоном операторе


9.5 Оператор Do

Из книги Давайте создадим компилятор! автора Креншоу Джек

9.5 Оператор Do Оператор do имеет видdo оператор while ( выражение ) ;Выполнение подоператора повторяется до тех пор, пока значение остается не нулем. Проверка выполняется после каждго выполнения оператора. Выражение обрабатывается как в уловном операторе


9.6 Оператор For

Из книги Базы данных: конспект лекций автора Автор неизвестен

9.6 Оператор For Оператор for имеет видfor (оператор_1 выражение_1 opt; выражение_2 opt) оператор_2Этот оператор эквивалентен следующему:оператор_1 while ( выражение_1 ) (* оператор_2 выражение_2 ; *)за исключением того, что continue в операторе_2 будет выполнять выражение_2 перед выполнением


Оператор IF

Из книги Программирование автора Козлова Ирина Сергеевна


Оператор WHILE

Из книги Справочное руководство по C++ автора Страустрап Бьярн


Оператор DO

Из книги Delphi. Учимся на примерах автора Парижский Сергей Михайлович


1. Оператор Select – базовый оператор языка структурированных запросов

Из книги Обработка баз данных на Visual Basic®.NET автора Мак-Манус Джеффри П

1. Оператор Select – базовый оператор языка структурированных запросов Центральное место в языке структурированных запросов SQL занимает оператор Select, с помощью которого реализуется самая востребованная операция при работе с базами данных – запросы.Оператор Select


R.6.4.1 Оператор if

Из книги Linux и UNIX: программирование в shell. Руководство разработчика. автора Тейнсли Дэвид

R.6.4.1 Оператор if Выражение должно быть арифметического типа, или типа указателя, или типа класс, для которого существует однозначное преобразование в арифметический тип или тип указателя (§R.12.3).Вычисляется выражение, и если оно имеет отличный от нуля результат,


Оператор if

Из книги C++ для начинающих автора Липпман Стенли

Оператор if Оператор if имеет синтаксис двух видов:if выражение then блок_кода;if выражение then блок_кода else блок_кода;Если выражение возвращает значение True, то выполняется блок кода, расположенный после ключевого слова then, в противном случае выполняется или программный код,


Правило 52: Если вы написали оператор new с размещением, напишите и соответствующий оператор delete

Из книги автора

Правило 52: Если вы написали оператор new с размещением, напишите и соответствующий оператор delete Операторы new и delete с размещением встречаются в C++ не слишком часто, поэтому в том, что вы с ними не знакомы, нет ничего страшного. Вспомните (правила 16 и 17), что когда вы пишете такое


3.3. Оператор &

Из книги автора

3.3. Оператор & При выполнении задания в экранном режиме происходит "захват" терминала на весь этот период. Перевод задания в фоновый режим позволяет освободить терминал для других целей. Чтобы выполнить команду в фоновом режиме, укажите после нее оператор &:команда


6.1. Оператор &&

Из книги автора

6.1. Оператор && Общий формат оператора && таков:команда1 && команда2Эта инструкция обрабатывается следующим образом: правый операнд интерпретируется только тогда, когда левый операнд равен TRUE. Иными словами, вторая команда выполняется в том случае, если первая


15.8.2. Оператор размещения new() и оператор delete()

Из книги автора

15.8.2. Оператор размещения new() и оператор delete() Оператор-член new() может быть перегружен при условии, что все объявления имеют разные списки параметров. Первый параметр должен иметь тип size_t:class Screen {public:void *operator new( size_t );void *operator new( size_t, Screen * );// ...};Остальные параметры