Dynamic_cast
Операция динамического приведения типа
dynamic сast<целевой_тип>(аргумент)
не имеет аналогов среди операций, выполняемых .с применением “классической” нотации приведения. Операция и проверка ее корректности при известных условиях происходит во время выполнения программы.
Динамическое приведение типа опирается на механизм RTTI, поэтому необходимо установить флажок Enable RTTI в диалоге Project Options (страница C++). Если этот флажок сброшен, программа компилироваться не будет.
Целевой тип операции должен быть типом указателя, ссылки или void*. Если целевой тип — тип указателя, то аргументом должен быть указатель на объект класса; если целевой тип — ссылка, то аргумент должен также быть соответствующей ссылкой. Если целевым типом является void*, то аргумент также должен быть указателем, а результатом операции будет указатель, с помощью которого можно обратиться к любому элементу “самого производного” класса иерархии, который сам не может быть базовым ни для какого другого класса.
Приведение от производного класса к базовому разрешается на этапе компиляции. Преобразования от базового класса к производному, либо перекрестные преобразования на некоторой иерархии, происходят во время выполнения программы. Операция нисходящего приведения типа допустима только в случае, если базовый класс (класс аргумента) является полиморфным.
При попытке произвести некорректное преобразование операция возвращает нуль, если целевой_тип — указатель. Если ссылка, операция выбрасывает исключение типа bad_cast.
С помощью операции dynamic_cast можно выполнять нисходящее приведение виртуального базового класса, что невозможно сделать с применением обычной нотации приведений, при условии, что базовый класс является полиморфным и преобразование разрешается однозначно.
Ниже показаны две программы, демонстрирующие динамическое приведение типа. В первой из них для контроля успешности преобразований используются исключения, во второй — проверка на равенство результата нулю.
Листинг 13.3. Нисходящее и перекрестное приведение типа
//////////////////////////////////////////////////
// Dynamic.срр: Динамическое приведение типа.
//
#include <iostream.h>
#include <typeinfo.h>
#pragma hdrstop
#include <condefs.h>
class Bl { // Полиморфный базовый класс.
public:
virtual ~B1() {} } ;
class B2 {}; class D:
public Bl,
public B2 {}; // Производный класс.
int main () {
D d;
Bl bl;
Bl &rbl = d;
try { //
// Нисходящее приведение. //
cout <<" Downcasting from Bl; object ID: "
<< typeid(rbl).name() << endl;
D &rd = dynamic_cast<DS>(rbl);
cout << "OK..."<< endl;
//
// Перекрестное приведение.
//
cout << "Cross-castind from Bl to B2; object ID: "
<< typeid(rbl).name() << endl;
B2 &rb2 = dynamic_cast<B2&> (rbl);
cout << "OK..." << endl;
//
// Попытка недопустимого приведения.
//
Bl &rrbl = bl;
cout << "Try invalid cross-casting; object ID:"
<< typeid(rrbl).name() << endl;
B2 &rrb2 = dynamic_ca3t<B2&>(rrbl);
cout << "OK..." << endl;
} catch(bad_cast) {
cout << "Cast failed." << endl;
} catch(bad_typeid) {
cout << "Typeid failed." << end1;
}
return 0;
}
Вывод программы:
Downcasbing from Bl; object. ID: D
OK. . .
Cross-castind from Bl to B2; object ID: D
OK. . .
Try invalid cross-casting; object ID: Bl
Cast failed.
Перекрестное приведение типа, т. е. такое, при котором классы хотя и относятся к одной иерархии, но находятся на разных ее “ветвях”, допускается и для обычных приведений, но в этом случае вряд ли вы получите сколько-нибудь осмысленный результат.
Действие операции dynamic_cast при перекрестном приведении типов можно представить следующим образом. Сначала ищется наивысший класс иерархии, являющий производным сразу от обоих классов, участвующих в преобразовании. Указатель приводится к этому классу (нисходящее преобразование). После этого он возводится до класса результата (преобразование от производного класса к базовому, что можно сделать всегда).
Листинг 13.4. Приведение от виртуального базового класса
/////////////////////////////////////////////////
// VBaseCast.срр: Приведение виртуального базового класса.
//
#include <iostream.h>
#include <typeinfo.h>
#pragma hdrstop
#include <condefs.h>
class VBase { // Виртуальный базовый класс. public:
virtual ~VBase() {} 1;
class Bl: public virtual VBase {};
class B2: public virtual VBase {};
class D: public Bl, public B2 {}; // Производный класс.
//
// Вспомогательная функция, аргументом которой
// может' быть любой класс данной иерархии.
//
void Report(VBase *pvb)
{
try {
cout << " ... Object ID: "
<< typeid (*pvb).name() << endl;
}
catch(bad_typeid) {
cout << " ...'Bad typeid in Report()."<< endl;
}
xin-. ilia in ( ) {
D d;
Bl bl;
{
Base *pvb = &d;
cout << "Original class: " << typeid(*pvb).name();
//не корректное приведение - pvb ссылается на объект //Производного класса.
//
Report (dynamic_cast<D*> (pvb) ) ;
pvb = o.al;
cout<< "Original class: " << typeid(*pvb).name();
//
// Следующее приведение не удается, поскольку объект,
// на который ссылается pvb, не является D. В Report()
// выбрасывается.bad_typeid, т.к. аргумент нулевой.
//
Report(dynamic cast<D*>(pvb));
} catch(bad__typeid) {
cout << " ... Bad typeid in main()." << end1;
}
return 0;
}
Программа выводит:
Original class: D ... Object ID: D
Original class: B1 ... Bad typeid in Report ().
При преобразованиях типа на некоторой иерархии, особенно нисходящих и перекрестных, применяйте операцию dynamic_cast, а не статическое приведение. Динамическое приведение типа обладает неоспоримыми преимуществами и к тому же не вносит в код никаких дополнительных издержек, если приведение можно осуществить на этапе компиляции. Другими словами, в таких случаях, если это достаточно безопасно, dynamic_cast генерирует точно такой же код, что и static_cast.
Заключение
Управление исключениями, идентификация типа времени выполнения и специальные операции приведения составляют в своем роде единство, которое, при грамотном к нему подходе, позволяет значительно повысить надежность и устойчивость создаваемого программного обеспечения. Конечно, ко всему этому придется довольно долго привыкать, если раньше вы программировали, скажем, только на традиционном С.
Этой главой мы заканчиваем описание стандартного языка C++. Следующая часть книги посвящена визуальным средствам C++Builder и связанными с ними особенностям языка этой системы.