Namespace CounterNameSpace 14 страница

 

// Враховуємо цей об'єкт

if(typeid(*p) == typeid(triAngle)) t++;

if(typeid(*p) == typeid(rectAngle)) r++;

if(typeid(*p) == typeid(cirCle)) c++;

 

// Відображаємо площу фігури

cout << "Площа дорівнює " << p->area() << endl;

}

cout << endl;

cout << "Згенеровано такі об'єкти:\n";

cout << " трикутників: " << t << endl;

cout << " прямокутників: " << r << endl;

cout << " кругів: " << c << endl;

 

getch(); return 0;

}

Можливий результат виконання цієї програми такий:

Об'єкт має тип class rectAngle. Площа дорівнює 24.51

Об'єкт має тип class rectAngle. Площа дорівнює 24.51

Об'єкт має тип class triAngle. Площа дорівнює 26.765

Об'єкт має тип class triAngle. Площа дорівнює 26.765

Об'єкт має тип class rectAngle. Площа дорівнює 24.51

Об'єкт має тип class triAngle. Площа дорівнює 26.765

Об'єкт має тип class cirCle. Площа дорівнює 314

Об'єкт має тип class cirCle. Площа дорівнює 314

Об'єкт має тип class triAngle. Площа дорівнює 26.765

Об'єкт має тип class rectAngle. Площа дорівнює 24.51

 

Згенеровано такі об'єкти:

трикутників: 4

прямокутників: 4

кругів: 2

20.1.3. Застосування оператора typeid до шаблонних класів

Оператор typeid можна застосувати і до шаблонних класів. Тип об'єкта, який є примірником шаблонного класу, визначається частково на підставі того, які саме дані використовуються для його узагальнених даних під час реалізації об'єкта. Таким чином, два примірники одного і того ж шаблонного класу, які створюються з використанням різних даних, мають різний тип. Розглянемо простий приклад.

Код програми 20.5. Демонстрація механізму застосування оператора typeid до шаблонних класів

#include <iostream>// Для потокового введення-виведення

#include <cstdlib> // Для використання бібліотечних функцій

using namespace std; // Використання стандартного простору імен

 

template <class demoClass> class myClass { // Оголошення класового типу

demoClass а;

public:

myClass(demoClass izm) { a = izm;}

//. . .

};

 

int main()

{

myClass<int> A_ob(10), B_ob(9);

myClass<double> C_ob(7.2);

cout << "Об'єкт A_ob має тип ";

cout << typeid(A_ob).name() << endl;

cout << "Об'єкт B_ob має тип ";

cout << typeid(B_ob).name() << endl;

cout << "Об'єкт C_ob має тип ";

cout << typeid(C_ob).name() << endl;

cout << endl;

if(typeid(A_ob) == typeid(B_ob))

cout << "Об'єкти A_ob і B_ob мають однаковий тип.\n";

if(typeid(A_ob) == typeid(C_ob))

cout << "Помилка\n";

cout << "Об'єкти A_ob і C_ob мають різні типи.\n";

 

getch(); return 0;

}

Внаслідок виконання цієї програми на моніторі буде відображено такі результати:

Об'єкт A_ob має тип class myClass<int>

Об'єкт B_ob має тип class myClass<int>

Об'єкт C_ob має тип class myClass<double>

 

Об'єкти A_ob і B_ob мають однаковий тип.

Об'єкти A_ob і C_ob мають різні типи.

Як бачите, незважаючи на те, що два об'єкти є примірниками одного і того ж шаблонного класу, якщо їх дані, що параметризуються, не збігаються, то вони не є еквіва|лентними| за типом. У цій програмі об'єкт A_ob має тип myClass<int>, а об'єкт C_ob – тип myClass<double>. Таким чином, це об'єкти різного типу.

Розглянемо ще один приклад застосування оператора typeid до шаблонних класів, а саме модифіковану версію програми визначення геометричних фігур з попереднього підрозділу. Цього разу клас figUre ми зробили шаблонним.

Код програми 20.6. Демонстрація механізму застосування оператора typeid до шаблонної версії figUre-ієрархії класів

#include <iostream>// Для потокового введення-виведення

#include <cstdlib> // Для використання бібліотечних функцій

using namespace std; // Використання стандартного простору імен

template <class demoClass> class figUre {

protected:

demoClass х, y;

public:

figUre(demoClass izm, demoClass jzm) { x = izm; y = jzm;}

virtual demoClass area() = 0;

};

 

template <class demoClass> class triAngle: public figUre<demoClass> {

public:

triAngle(demoClass izm, demoClass jzm): figUre<demoClass>(izm, jzm) {}

demoClass area() { return x * 0.5 * y; }

};

 

template <class demoClass> class rectAngle: public figUre<demoClass> {

public:

rectAngle(demoClass izm, demoClass jzm): figUre<demoClass>(izm, jzm) {}

demoClass area() { return x * y;}

};

 

template <class demoClass> class cirCle: public figUre<demoClass> {

public:

cirCle(demoClass izm, demoClass jzm=0): figUre<demoClass>(izm, jzm) {}

demoClass area() { return 3.14 * x * x;}

};

 

// Генератор об'єктів, що утворюється з класу figUre.

figUre<double> *generator()

{

switch(rand() % 3) {

case 0: return new cirCle<double>(10.0);

case 1: return new triAngle<double>(10.1, 5.3);

case 2: return new rectAngle<double>(4.3, 5.7);

}

return;

}

 

int main()

{

figUre<double> *p;

int i;

int t = 0, c = 0, r = 0;

 

// Генеруємо і підраховуємо об'єкти

for(i=0; i<10; i++) {

p = generator();

cout << "Об'єкт має тип " << typeid(*p).name();

cout << ". ";

 

// Враховуємо об'єкт

if(typeid(*p) == typeid(triAngle<double>)) t++;

if(typeid(*p) == typeid(rectAngle<double>)) r++;

if(typeid(*p) == typeid(cirCle<double>)) c++;

cout << "Площа дорівнює " << p->area() << endl;

}

cout << endl;

cout << "Згенеровано такі об'єкти:\n";

cout << " трикутників: " << t << endl;

cout << " прямокутників: " << r << endl;

cout << " кругів: " << c << endl;

 

getch(); return 0;

}

Ось як виглядає можливий результат виконання цієї програми:

Об'єкт має тип class rectAngle<double>. Площа дорівнює 24.51

Об'єкт має тип class rectAngle<double>. Площа дорівнює 24.51

Об'єкт має тип class triAngle<double>. Площа дорівнює 26.765

Об'єкт має тип class triAngle<double>. Площа дорівнює 26.765

Об'єкт має тип class rectAngle<double>. Площа дорівнює 24.51

Об'єкт має тип class triAngle<double>. Площа дорівнює 26.765

Об'єкт має тип class cirCle<double>. Площа дорівнює 314

Об'єкт має тип class cirCle<double>. Площа дорівнює 314

Об'єкт має тип class triAngle<double>. Площа дорівнює 26.765

Об'єкт має тип class rectAngle<double>. Площа дорівнює 24.51

 

Згенеровано такі об'єкти:

трикутників: 4

прямокутників: 4

кругів: 2

Динамічна ідентифікація типів використовується не у кожній програмі. Але під час роботи з поліморфними типами цей засіб дає змогу дізнатися про тип об'єкта, що обробляється в будь-який довільний момент часу.

20.2. Оператори приведення типів

У мові програмування C++ визначено п'ять операторів приведення типів. Перший оператор (він був описаний вище у цьому навчальному посібнику), вживається у звичайному (традиційному) стилі, був із самого початку вбудований у мові C++. Інші чотири (dynamic_cast, const_cast, reinterpret_cast і static_cast) були додані у мову всього декілька років тому. Ці оператори надають додаткові "важелі керування" характером виконання операцій приведення типу. Розглянемо кожний з них зокрема.

20.2.1. Оператор приведення поліморфних типів dynamic_cast

Оператор dynamic_cast виконує операцію приведення поліморфних типів у процесі виконання програми.

Можливо, найважливішим з нових операторів є оператор динамічного приведення типів dynamic_cast. У процесі виконання програми він перевіряє обґрунтованість пропонованої операції. Якщо у момент його виклику задана операція виявляється неприпустимою, то приведення типів не виконується. Загальний формат застосування оператора dynamic_cast є таким:

dynamic_cast<type> (expr)

У цьому записі елемент type означає новий тип, який є метою виконання цієї операції, а елемент ехрr – вираз, що приводиться до цього нового типу. Тип type повинен бути представленим покажчиком або посиланням, а вираз ехрr повинен приводитися до покажчика або посилання. Таким чином, оператор dynamic_cast можна використовувати для перетворення покажчика одного типу у покажчик іншого або посилання одного типу у посилання іншого.

Цей оператор в основному використовують для динамічного виконання операцій приведення типу серед поліморфних типів. Наприклад, якщо задано поліморфні класи В і D, причому клас D виведений з класу В, то за допомогою оператора dynamic_cast завжди можна перетворити покажчик D* у покажчик В*, оскільки покажчик на базовий клас завжди можна використовувати для вказівки на об'єкт класу, виведеного з базового. Проте оператор dynamic_cast може перетворити покажчик В* у покажчик D* тільки у тому випадку, якщо адресованим об'єктом дійсно є об'єкт класу D. І, взагалі, оператор dynamic_cast буде успішно виконаний тільки за умови, якщо дозволено поліморфне приведення типів, тобто якщо покажчик (або посилання), що приводиться до нового типу, може вказувати (або посилатися) на об'єкт цього нового ти|пу або об'єкт, який є виведеним з нього. Інакше, тобто якщо задану операцію приведення типів виконати не можна, то результат дії оператора dynamic_cast оцінюється як нульовий, якщо у цій операції беруть участь покажчики[81].

Розглянемо простий приклад. Припустимо, що клас Base поліморфний, а клас Derived виведений з класу Base.

Base *bp, B_ob;

Derived *dp, D_ob;

 

bp = &D_ob; // Присвоєння адреси об'єкта похідного класу

// Покажчик на базовий клас вказує

// на об'єкт класу Derived.

dp = dynamic_cast<Derived *> (bp); // Приведення до покажчика на

// похідний клас дозволено.

if(dp) cout << "Приведення типу пройшло успішно!";

Тут приведення покажчика bp (на базовий клас) до покажчика dp (на похідний клас) успішно виконується, оскільки bp дійсно вказує на об'єкт класу Derived. Тому у процесі виконання цього фрагмента коду програми буде виведено повідомлення:

Приведення типу пройшло успішно!

Але наведений нижче фрагмент коду програми демонструє невдалу спробу зробити операцію приведення типу, оскільки bp насправді вказує на об'єкт класу Base, і неправомірно приводити покажчик на базовий клас до типу покажчика на похідний, якщо адресований ним об'єкт не є насправді об'єктом похідного класу.

bp = &B_ob; // Присвоєння адреси об'єкта базового класу

// Покажчик на базовий клас посилається

// на об'єкт класу Base.

dp = dynamic_cast<Derived *> (bp); // Помилка!

if(!dp) cout << "Приведення типу виконати не вдалося";

Оскільки спроба виконати операцію приведення типу виявилася невдалою, то у процесі виконання цього фрагмента коду програми буде виведено повідомлення:

Приведення типу виконати не вдалося.

У наведеному нижче коді програми демонструються різні ситуації застосування оператора dynamic_cast.

Код програми 20.7. Демонстрація різних ситуацій застосування використання оператора dynamic_cast

#include <iostream>// Для потокового введення-виведення

using namespace std; // Використання стандартного простору імен

 

class baseClass { // Оголошення базового класу

public:

virtual voidfun() { cout << "У класі Base.\n";}

//. . .

};

 

class Derived: public Base {

public:

voidfun() { cout << "У класі Derived.\n";}

};

 

int main()

{

Base *bp, B_ob;

Derived *dp, D_ob;

 

dp = dynamic_cast<Derived *> (&D_ob);

if(dp) {

cout << "Приведення типів "

<< "(з Derived * у Derived *) реалізовано.\n";

dp->fun();

}

cout << "Помилка\n";

cout << endl;

 

bp = dynamic_cast<Base *> (&D_ob);

if(bp) {

cout << "Приведення типів "

<< "(з Derived * у Base *) реалізовано.\n";

bp->fun();

}

cout << "Помилка\n";

cout << endl;

 

bp = dynamic_cast<Base *> (&B_ob);

if(bp) {

cout << "Приведення типів "

<< "(з Base * у Base *) реалізовано.\n";

bp->fun();

}

cout << "Помилка\n";

cout << endl;

 

dp = dynamic_cast<Derived *> (&B_ob);

if(dp) cout << "Помилка\n";

cout << "Приведення типів "

<< "(з Base * у Derived *) не реалізовано.\n";

cout << endl;

 

bp = &D_ob; // Присвоєння адреси об'єкта похідного класу

// bp вказує на об'єкт класу Derived

dp = dynamic_cast<Derived *> (bp);

if(dp) {

cout << "Приведення bp до типу Derived *\n"

<< "реалізовано, оскільки bp дійсно\n"

<< "вказує на об'єкт класу Derived.\n";

dp->fun();

}

cout << "Помилка\n";

cout << endl;

 

bp = &B_ob; // Присвоєння адреси об'єкта базового класу

// bp вказує на об'єкт класу Base

dp = dynamic_cast<Derived *> (bp);

if(dp)

cout << "Помилка";

else {

cout << "Тепер приведення bp до типу Derived *\n"

<< "не реалізовано, оскільки bp\n"

<< "насправді вказує на об'єкт класу Base.\n";

}

cout << endl;

 

dp = &D_ob; // Присвоєння адреси об'єкта похідного класу

// dp вказує на об'єкт класу Derived

bp = dynamic_cast<Base *> (dp);

if(bp) {

cout << "Приведення dp до типу Base * реалізовано.\n";

bp->fun();

}

cout << "Помилка\n";

 

getch(); return 0;

}

Програма відображає на екрані такі результати:

Приведення типів (з Derived * у Derived *) реалізовано.

У класі Derived.

 

Приведення типів (з Derived * у Base *) реалізовано.

У класі Derived.

 

Приведення типів (з Base * у Base *) реалізовано.

У класі Base.

 

Приведення типів (з Base * у Derived *) не реалізовано.

 

Приведення bp до типу Derived *

реалізовано, оскільки bp дійсно

вказує на об'єкт класу Derived.

У класі Derived.

 

Тепер приведення bp до типу Derived *

не реалізовано, оскільки bp

насправді вказує на об'єкт класу Base.

 

Приведення dp до типу Base * реалізовано.

У класі Derived.

Оператор dynamic_cast можна іноді використовувати замість оператора typeid. Наприклад, припустимо, що клас Base – поліморфний і є базовим для класу Derived, тоді у процесі виконання такого фрагмента коду програми покажчику dp буде присвоєно адресу об'єкта, яка адресується покажчиком bp, але тільки у тому випадку, якщо цей об'єкт дійсно є об'єктом класу Derived:

Base *bp;

Derived *dp;

//...

if(typeid(*bp) == typeid(Derived)) dp = (Derived *) bp;

У цьому випадку використовується звичайна операція приведення типів. Тут це цілком безпечно, оскільки настанова if перевіряє законність операції приведення типів за допомогою оператора typeid до її реального виконання. Те ж саме можна зробити ефективніше, замінивши операторів typeid і настанову if оператором

dynamic_cast:

dp = dynamic_cast<Derived *> (bp);

Оскільки оператор dynamic_cast успішно виконується тільки у тому випадку, якщо об'єкт, який піддається операції приведення до типу, вже є об'єктом або заданого типу, або типу, виведеного із заданого, то після завершення цієї настанови покажчик dp міститиме або нульове значення, або покажчик на об'єкт типу Derived. Окрім того, оскільки оператор dynamic_cast успішно виконується тільки у тому випадку, якщо задана операція приведення типів правомірна, то у певних ситуаціях її логіку можна спростити. У наведеному нижче коді програми показано, як оператора typeid можна замінити оператором dynamic_cast. Тут виконується один і той самий набір операцій двічі: спочатку з використанням оператора typeid, а потім – оператора dynamic_cast.

Код програми 20.8. Демонстрація механізму використання оператора dynamic_cast замість оператора typeid

#include <iostream>// Для потокового введення-виведення

#include <typeinfo>

using namespace std; // Використання стандартного простору імен

 

class baseClass { // Оголошення базового класу

public:

virtual voidfun() {}

};

class Derived: public Base {

public:

void derivedOnly() { cout << "Це об'єкт класу Derived.\n";}

};

 

int main()

{

Base *bp, B_ob;

Derived *dp, D_ob;

 

// ************************************

// Використання оператора typeid

// ************************************

bp = &B_ob; // Присвоєння адреси об'єкта базового класу

if(typeid(*bp) == typeid(Derived)) {

dp = (Derived *) bp;

dp->derivedOnly();

}

cout << "Операції приведення об'єкта типу Base до "

<< " типу Derived не здійснено.\n";

 

bp = &D_ob; // Присвоєння адреси об'єкта похідного класу

if(typeid(*bp) == typeid(Derived)) {

dp = (Derived *) bp;

dp->derivedOnly();

}

cout << "Помилка, приведення типу має "

<< "бути реалізовано!\n";

 

// ************************************

// Використання оператора dynamic_cast

// ************************************

bp = &B_ob; // Присвоєння адреси об'єкта базового класу

dp = dynamic_cast<Derived *> (bp);

if(dp) dp->derivedOnly();

cout << "Операції приведення об'єкта типу Base до "

<< " типу Derived не здійснено.\n";

bp = &D_ob; // Присвоєння адреси об'єкта похідного класу

dp = dynamic_cast<Derived *> (bp);

if(dp) dp->derivedOnly();

cout << "Помилка, приведення типу має "

<< "бути реалізовано!\n";

 

getch(); return 0;

}

Як бачите, використання оператора dynamic_cast спрощує логіку, необхідну для перетворення покажчика на базовий клас у покажчик на похідний клас. Ось як виглядають результати виконання цієї програми:

Операції приведення об'єкта типу Base до типу Derived не здійснено.

Це об'єкт класу Derived. Операції приведення об'єкта типу Base до

типу Derived не здійснено. Це об'єкт класу Derived.

Необхідно пам'ятати! Оператор dynamic_cast можна також використовувати стосовно шаблонних класів.

20.2.2. Оператор перевизначення модифікаторів const_cast

Оператор const_cast використовують для безпосереднього перевизначення модифікаторів const і/або volatile. Новий тип повинен збігатися з початковим, за винятком його атрибутів const або volatile. Найчастіше оператор const_cast використовують для видалення ознаки постійності (атрибуту const). Його загальний формат має такий вигляд:

const_cast<type> (expr)

У цьому записі елемент type задає новий тип операції приведення, а елемент expr| означає вираз, який приводиться до нового типу.

Оператор const_cast перевизначає модифікатори const і/або volatile.

Використання оператора const_cast продемонстровано в такій програмі.

Код програми 20.9. Демонстрація механізму використання оператора const_cast

#include <iostream>// Для потокового введення-виведення

using namespace std; // Використання стандартного простору імен

voidfun(const int *p)

{

int *v;

 

// Перевизначення const-атрибуту.

v = const_cast<int *> (p);

 

*v = 100; // Тепер об'єкт можна модифікувати

}

 

int main()

{

int x = 99;

cout << "Значення x до виклику функції fun(): " << x << endl;

 

fun(&x);

cout << "Значення x після виклику функції fun(): " << x << endl;

 

getch(); return 0;

}

Результати виконання цієї програми є такими:

Значення х до виклику функції fun(): 99

Значення х після виклику функції fun(): 100

Як бачите, змінна х була модифікована функцією fun(), хоча параметр, який вона приймає, задається як const-покажчик.

Необхідно наголосити на тому, що використання оператора const_cast для видалення const-атрибуту є потенційно небезпечним засобом. Тому поводьтеся з ним дуже обережно.

Варто знати! Видаляти const-атрибут здатний тільки оператор const_cast. Іншими словами, ні dynamic_cast, ні static_cast, ні reinterpret_cast не можна використовувати для зміни const-атрибуту об'єкта.

20.2.3. Оператор неполіморфного приведення типів static_cast

Оператор static_cast виконує операцію неполіморфного приведення типів.

Його можна використовувати для будь-якого стандартного перетворення. При цьому під час роботи програми ніяких перевірок на допустимість не виконується. Оператор static_cast має такий загальний формат запису:

static_cast<type> (expr)

У цьому записі елемент type задає новий тип операції приведення, а елемент expr означає вираз, який приводиться до цього нового типу.

Оператор static_cast, по суті, є замінником оригінального оператора приведення типів. Він тільки виконує неполіморфне перетворення. Наприклад, у процесі виконання наведеної нижче програми змінна типу float приводиться до типу int.

Код програми 20.10. Демонстрація механізму використання оператора static_cast

#include <iostream>// Для потокового введення-виведення

using namespace std; // Використання стандартного простору імен

 

int main()

{

int izm;

float fzm;