C++ Программирование в среде С++ Builder 5

         

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 и связанными с ними особенностям языка этой системы.


Содержание раздела