Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Тема8.doc
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
252.93 Кб
Скачать

8.2.5. Реалізація залежностей

Залежності в мові С++ реалізуються за допомогою покажчиків чи за допомогою спеціальних об’єктів. Наприклад, залежність «багато-до-одного» між класами Item та Group реалізована через покажчики:

class Item{

public:

virtual void cut ();

virtual void move (Length deltax, Length deltay)=0;

virtual Boolean pick (Length px; Length py)=0;

virtual void ungroup ()=0;

private:

Group* group;

friend group::add_item (Item*);

friend group::remove_item (Item*);

public:

Group* get_group () {return group;};

};

class group: public Item

{

public:

void cut ();

void move (Length deltax, Length deltay);

Boolean pick (Length px; Length py)=0;

void ungroup (){ };

private:

ItemSet* іtems;

public:

void add_item (Item*);

void remove_item (Item*);

ItemSet* get_іtems() {return items;}

};

Щоразу, коли до залежності додається (чи з неї вилучається) зв’язок, два показники повинні бути змінені:

void Group::add_item (Item*item);

{

item->group = this;

items->add (item);

}

void Group::remove_item (Item*item);

{

item->group =0;

items->remove (item);

}

Методи Group:: add … item та Group:: remove … item можуть змінювати приватні (private) атрибути класу Item, хоч вони визначені у його підкласі Group, оскільки вони визначені як дружні (friends)для класу Item.

У розглянутому прикладі опущені перевірки:

  1. чи не є графічний об’єкт, який влючається в групу, членом цієї групи (у цьому разі його не слід ще раз включати в групу);

  2. чи не є зазначений графічний об’єкт членом якоїсь іншої групи (у такому разі його не можна включати в групу й необхідно видати на екран відповідне повідомлення).

Іноді зв’язані між собою (залежні) об’єкти включають у так звані колективні класи. Як приклад такого класу розглянемо клас ItemSet (набір об’єктів):

Class ItemSet

{

public:

ItemSet(); //скласти порожній набір об’єктів

~ItemSet(); //знищити набір об’єктів

void add(Item*); //включити об’єкт у набір

void remove(Item*); //виключити об’єкт з набору

Boolean includes(Item*); //перевірити наявність об’єкта у наборі

Int size(Item*); //визначити кількість об’єктів у наборі

}

Колективні класи часто використовуються в бібліотеках класів. При роботі з колективними класами зручно використовувати ітератори, тобто об’єкти, за допомогою яких можна «розглянути» колектив.

Залежності між класами можна реалізувати і за допомогою спеціальних класів (кожний об’єкт такого класу описує зв’язок між об’єктами відповідних класів). У цьому разі атрибути класу відповідають атрибутам описуваної ним залежності.

8.2.6. Шаблони, шаблони-класи

У мові С++ можливе і параметричне програмування (програмування з використанням родових компонентів). Родові (параметризовані) компоненти мають властивість адаптуватися до конкретної ситуації, в якій такий компонент використовується. Це дозволяє розробляти достатньо універсальні, водночас високоефективні компоненти програм (зокрема, об’єкти).

Параметричне програмування у мові С++ реалізоване за допомогою шаблонів (template). У С++ визначено два види шаблонів: шаблони-класи та шаблони-функції.

Шаблони-класи можуть використовуватись різними способами, але найбільш очевидним є їх використання в якості адаптивних об’єктів пам’яті. Щоб навести найпростіший приклад, припустимо, що потрібно визначити об’єкти, які можуть зберігати по два значення, одне ціле, а інше символьне. Для цього ми могли б визначити

class pair … int … char {

public:

int first;

char second;

pair … int … char(int x, char y):first(x), second(y) {}

};

Після цього ми могли б написати, наприклад,

pair … int … char pair1(13,`a’);

cout<< pair1.first<< end1;

cout<< pair1.second<< end1;

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

class pair … bool … double {

public:

bool first;

double second;

pair … bool … double(bool x, double y): first(x), second(y) {}

};

і написати, наприклад:

pair … bool … double pair1 (true, 0.1);

cout<< pair1.first<< end1;

cout<< pair1.second<< end1;

Те саме можна було б здійснити для нескінченного числа пар типів, але шаблон-клас дозволяє охопити всі ці випадки єдиним визначенням:

template <class T1, class T2>

class pair {

public:

T1 first;

T2 second;

pair (T1 x, T2 y):first (x), second (y) {}

};

Останнє визначення класу написане з використанням двох імен довільних типів Т1 та Т2, які є типами-параметрами. Ці імена вводяться визначенням типів-параметрів за допомогою конструкції template <class T1, class T2>, яка означає, що можна подавати фактичні імена типів замість Т1 та Т2, отримує контрастний екземпляр шаблона-класу pair. Наприклад, конструкції

pair <int, char> pair3 (13, ‘a’);

pair <bool, double> part4 (true, 0.1);

оголошують об’єкти pair3 та pair4, які структурно тотожні об’єктам pair1 та pair2 відповідно.

Але тепер можна використати pair у незчисленній множині інших випадків з іншими комбінаціями фактичних типів, як наприклад

pair <double, long > pair5 (3.1415, 999);

pair <bool, bool > pair6 (false, true);

Як фактичні типи можуть використовуватися типи, визначені через інші класи; наприклад

pair <pair … int … char, float > pair7 (pair1, 1.23);

чи, що еквівалентно,

pair <pair<int, char>, float > pair8(pair3, 1.23);

Як показано тут, можна використовувати типи, які одержані як примірники (представники) шаблонів-класів, всюди, де можна використовувати звичайні типи.

Визначення шаблона-класу pair є дуже простим прикладом параметричного контейнерного класу; проте його можна адаптувати до багатьох різних застосувань. Звичайне перетворення звичайного класу на параметризований (оскільки воно знаходить застосування в найширшому класі прикладних програм) більш корисне, ніж просто додавання ключового слова template та списку параметрів типів. Можуть бути корисними деякі модифікації, щоб бути впевненим у тім, що результуючі примірники об’єктів так само ефективні, як і об’єкти, визначені звичайним способом, незалежно від того, які типи фактично будуть підставлені. У разі, коли визначення шаблона-класу таке просте, як pair, то можна зробити поліпшення. Визначення шаблона-класу було дуже простим:

pair (T1 x, T2 y);

Проблема в тому, що х та у передаються за значенням. Це означає, що якщо ці об’єкти великі, то при виклику конструктора будуть робитися їх додаткові копії, а це дорого коштує. Щоб уникнути цього, потрібно передавати х та у як константні посилальні параметри, щоб передавалися лише адреси. Саме таким чином визначається pair в STL:

template <class T1, class T2>

class pair {

public:

T1 first;

T2 second;

pair (T1& x, T2& y):first (x), second (y) {}

};

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

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

int intmax (int x, int y)

{

return x<y?y:x;

}

Вона може визначити максимальне тільки із двох цілих (int), але її легко узагальнити, перетворивши на шаблон-функцію:

template <class T>

T max(T x, T y)

{

return x < y ? y : x;

}

Основна відмінність шаблона-функції від шаблона-класу в тому, що не потрібно повідомляти компілятор, до яких типів параметрів застосовується функція, він сам може визначити це за типами її формальних параметрів.

int u = 3, v = 4;

double d = 4.7;

cout << max(u, v) << endl; //мається на увазі тип int

cout << max(d, 9.3) << endl; //мається на увазі тип double

cout << max(u, d) << endl; //помилка: невідповідність типів

Компілятор потребує, щоб через х та у передавалися значення однакових типів, оскільки в шаблоні для них було вказано один і той самий тип Т. Потрібно також, щоб було визначення операції < у вигляді списку її параметрів (Т, Т). Наприклад, повертаючись до використання шаблона-класу pair, визначеного в попередньому пунктi,

pair <double, long> pair 5(3.1415, 999);

компілятор видає помилку при компіляції оператора:

max (pair 5, pair 5);

через відсутність визначення операції operator < для об’єктів типу pair<double, long>. Але він буде компілювати, якщо попередньо ви­значити зміст операції operator < для об’єктів цього типу, наприклад:

pair <double, long> operator< (const pair <double, long)& x, const pair <double, long>& y)

//порівняти х та у за їх першими членами:

{

return x.first <y.first;

}

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

У висновку визначимо, що наведене визначення шаблона-функ­ції max може бути поліпшене так само, як це було зроблено для конструктора шаблона-класу pair, тобто введенням константних посилальних параметрів:

template <class T>

T max(const T& x, const T& y)

{

return x < y ? y : x;

}

Саме таке визначення використовується в STL.

Шаблони-функції як функції-члени

У визначенні звичайних класів чи шаблонів-класів функції-члени можуть мати шаблони в якості параметрів (у доповнення до шаблонів, які є параметрами класів). Ця нова властивість мови С++ використовується в шаблонах-класах при визначенні уміщуючих класів (контейнерів) STL. Наприклад, кожний уміщуючий клас має функцію-член insert, у якій є шаблон-параметр для визначення типу використовуваного ітератора:

template <class T>

сlass vector {

...

template <class Iterator >

insert(Iterator first, Iterator last);

...

};

Як буде показано далі, ця функція-член insert може використовуватися для того, щоб вставити елементи, які скопіювали з деякої іншої структури (наприклад, списку), використовуючи зв’язані з цією структурою ітератори. Визначення такої функції-члена як шаблона-функції, котрий має тип ітератора як параметра шаблона, робить її більш корисною, ніж коли б тип ітератора був у неї фіксований.

На жаль, надалі велика частина компіляторів С++ не підтримує функції-члени, які є шаблонами, що обмежує можливості STL.