Смекни!
smekni.com

Розробка власного класу STRING (стр. 10 из 16)

Можна в такий спосіб зобразити состав об'єкта класу window_w_border_and_menu:

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

Тепер треба написати всі ці функції draw (). Це не занадто важко, але для необережного програміста тут є пастка. Спочатку підемо найпростішим шляхом, що саме до неї й веде:

void window_w_border:: draw ()

{

window:: draw ();

// малюємо рамку

}

void window_w_menu:: draw ()

{

window:: draw ();

// малюємо меню

}

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

void clock:: draw () // пастка!

{

window_w_border:: draw ();

window_w_menu:: draw ();

// тепер операції, що ставляться тільки

// до вікна з рамкою й меню

}

На перший погляд все цілком нормально. Як звичайно, спочатку виконуються всі операції, необхідні для базових класів, а потім ті, які ставляться властиво до похідних класів. Але в результаті функція window:: draw () буде викликатися двічі! Для більшості графічних програм це не просто зайвий виклик, а псування картинки на екрані. Звичайно друга видача на екран затирає першу.

Щоб уникнути пастки, треба діяти не так поспішно. Ми відокремимо дії, виконувані базовим класом, від дій, виконуваних з базового класу. Для цього в кожному класі введемо функцію _draw (), що виконує потрібні тільки для нього дії, а функція draw () буде виконувати ті ж дії плюс дії, потрібні для кожного базового класу. Для класу window зміни зводяться до введення зайвої функції:

class window {

// головна інформація

void _draw ();

void draw ();

};

Для похідних класів ефект той же:

class window_w_border: public virtual window {

// клас "вікно з рамкою"

// визначення, пов'язані з рамкою

void _draw ();

void draw ();

};

void window_w_border:: draw ()

{

window:: _draw ();

_draw (); // малює рамку

};

Тільки для похідного класу наступного рівня проявляється відмінність функції, що і дозволяє обійти пастку з повторним викликом window:: draw (), оскільки тепер викликається window:: _draw () і тільки один раз:

class clock

: public virtual window,

public window_w_border,

public window_w_menu {

void _draw ();

void draw ();

};

void clock:: draw ()

{

window:: _draw ();

window_w_border:: _draw ();

window_w_menu:: _draw ();

_draw (); // тепер операції, що ставляться тільки

// до вікна з рамкою й меню

}

Не обов'язково мати обидві функції window:: draw () і window:: _draw (), але наявність їх дозволяє уникнути різних простих описок.

У цьому прикладі клас window служить сховищем загальної для window_w_border і window_w_menu інформації й визначає інтерфейс для спілкування цих двох класів.

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

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

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

class window {

// головна інформація

virtual void draw ();

virtual void get_input ();

};

В одному з похідних класів можна використати цю функцію, не замислюючись про те, де вона визначена:

class window_w_banner: public virtual window {

// клас "вікно із заголовком"

void draw ();

void update_banner_text ();

};

void window_w_banner:: update_banner_text ()

{

// ...

get_input ();

// змінити текст заголовка

}

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

class window_w_menu: public virtual window {

// клас "вікно з меню"

// визначення, пов'язані з меню

void draw ();

void get_input (); // перевизначає window:: get_input ()

};

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

class clock

: public virtual window,

public window_w_banner,

public window_w_menu

{

void draw ();

};

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

class window_w_input: public virtual window {

// ...

void draw ();

void get_input (); // перевизначає window:: get_input

};

class clock

: public virtual window,

public window_w_input,

public window_w_menu

{ // помилка: обидва класи window_w_input і

// window_w_menu перевизначають функцію

// window:: get_input

void draw ();

};

Транслятор виявляє подібну помилку, а усунути неоднозначність можна звичайним способом: ввести в класи window_w_input і window_w_menu функцію, що перевизначає "функції-порушника", і якимось чином усунути неоднозначність:

class window_w_input_and_menu

: public virtual window,

public window_w_input,

public window_w_menu

{

void draw ();

void get_input ();

};

У цьому класі window_w_input_and_menu:: get_input () буде перевизначати всі функції get_input ().

1.14.11 Контроль доступу

Член класу може бути приватним (private), захищеним (protected) або загальним (public):

Приватний член класу X можуть використати тільки функції-члени й друзі класу X.

Захищений член класу X можуть використати тільки функції-члени й друзі класу X, а так само функції-члени й друзі всіх похідних від X класів.

Загальний член можна використати в будь-якій функції.

Ці правила відповідають розподілу функцій, що звертаються до класу, на три види: функції, що реалізують клас (його друзі й члени), функції, що реалізують похідний клас (друзі й члени похідного класу) і всі інші функції.

Контроль доступу застосовується одноманітно до всіх імен. На контроль доступу не впливає, яку саме сутність позначає ім'я. Це означає, що частками можуть бути функції-члени, константи й т.д. нарівні із приватними членами, що представляють дані:

class X {

private:

enum { A, B };

void f (int);

int a;

};

void X:: f (int i)

{

if (i<A) f (i+B);

a++;

}

void g (X& x)

{

int i = X:: A; // помилка: X:: A приватний член

x. f (2); // помилка: X:: f приватний член

x. a++; // помилка: X:: a приватний член

}

1.14.12 Захищені члени

Дамо приклад захищених членів, повернувшись до класу window з попереднього розділу. Тут функції _draw () призначалися тільки для використання в похідних класах, оскільки надавали неповний набір можливостей, а тому не були достатньо зручні й надійні для загального застосування. Вони були як би будівельним матеріалом для більше розвинених функцій.

З іншого боку, функції draw () призначалися для загального застосування.

Це розходження можна виразити, розбивши інтерфейси класів window на дві частини - захищений інтерфейс і загальний інтерфейс:

class window {

public:

virtual void draw ();

// ...

protected:

void _draw ();

// інші функції, що служать будівельним матеріалом

private:

// подання класу

};

Така розбивка можна проводити й у похідних класах, таких, як window_w_border або window_w_menu.

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

От менш практичний, але більше докладний приклад:

class X {

// за замовчуванням приватна частина класу

int priv;

protected:

int prot;

public:

int publ;

void m ();

};

Для члена X:: m доступ до членів класу необмежений:

void X:: m ()

{

priv = 1; // нормально

prot = 2; // нормально

publ = 3; // нормально

}

Член похідного класу має доступ тільки до загальних і захищених членів:

class Y: public X {

void mderived ();

};

Y:: mderived ()

{

priv = 1; // помилка: priv приватний член

prot = 2; // нормально: prot захищений член, а

// mderived () член похідного класу Y

publ = 3; // нормально: publ загальний член

}

У глобальній функції доступні тільки загальні члени:

void f (Y* p)

{

p->priv = 1; // помилка: priv приватний член

p->prot = 2; // помилка: prot захищений член, а f ()

// не друг або член класів X і Y

p->publ = 3; // нормально: publ загальний член}

1.14.13 Доступ до базових класів

Подібно члену базовий клас можна описати як приватний, захищений або загальний:

class X {

public:

int a;

// ...

};

class Y1: public X { };

class Y2: protected X { };

class Y3: private X { };

Оскільки X - загальний базовий клас для Y1, у будь-якій функції, якщо є необхідність, можна (неявно) перетворити Y1* в X*, і притім у ній будуть доступні загальні члени класу X:

void f (Y1* py1, Y2* py2, Y3* py3)

{

X* px = py1; // нормально: X - загальний базовий клас Y1

py1->a = 7; // нормально

px = py2; // помилка: X - захищений базовий клас Y2

py2->a = 7; // помилка

px = py3; // помилка: X - приватний базовий клас Y3

py3->a = 7; // помилка

}

Тепер нехай описані

class Y2: protected X { };

class Z2: public Y2 { void f (); };

Оскільки X - захищений базовий клас Y2, тільки друзі й члени Y2, а також друзі й члени будь-яких похідних від Y2 класів (зокрема Z2) можуть при необхідності перетворювати (неявно) Y2* в X*. Крім того вони можуть звертатися до загальних і захищених членів класу X: