Коли ми пишемо:
char *arena = new char [sizeof Image];
то з купи виділяється пам'ять, розмір якої дорівнює розміру об'єкта типу Image, вона не ініціалізована й заповнена випадковими бітами.
Якщо ж написати:
Image *ptr = new (arena) Image ("Quasimodo");
то ніякої нової пам'яті не виділяється. Замість цього змінної ptr привласнюється адреса, асоційованою зі змінною arena. Тепер пам'ять, на яку вказує ptr, інтерпретується як займана об'єктом класу Image, і конструктор застосовується до вже існуючої області. Таким чином, оператор розміщення new () дозволяє сконструювати об'єкт у раніше виділеній області пам'яті.
Закінчивши працювати із зображенням Quasimodo, ми можемо зробити якісь операції із зображенням Esmerelda, розміщеним по тій же адресі arena у пам'яті:
Image *ptr = new (arena) Image ("Esmerelda");
Однак зображення Quasimodo при цьому буде затерто, а ми його модифікували й хотіли б записати на диск. Звичайне збереження виконується в деструкторі класу Image, але якщо ми застосуємо оператор delete:
// погано: не тільки викликає деструктор, але й звільняє пам'ять
delete ptr;
то, крім виклику деструктора, ще й повернемо в купу пам'ять, чого робити не варто було б. Замість цього можна явно викликати деструктор класу Image:
ptr->~Image ();
зберігши відведену під зображення пам'ять для наступного виклику оператора розміщення new.
Відзначимо, що, хоча ptr і arena адресують ту саму область пам'яті в купі, застосування оператора delete до arena
// деструктор не викликається
delete arena;
не приводить до виклику деструктора класу Image, тому що arena має тип char*, а компілятор викликає деструктор тільки тоді, коли операндом в delete є вказівник на об'єкт класу, що має деструктор.
Вбудований деструктор може стати причиною непередбаченого збільшення розміру програми, оскільки він вставляється в кожній точці виходу всередині функції для кожного активного локального об'єкта. Наприклад, у наступному фрагменті
Account acct ("Tina Lee");
int swt;
// ...
switch (swt) {
case 0:
return;
case 1:
// щось зробити
return;
case 2:
// зробити щось інше
return;
// і так далі
}
компілятор підставить деструктор перед кожною інструкцією return. Деструктор класу Account невеликий, і витрати часу й пам'яті на його підстановку теж малі. У противному випадку прийдеться або об’явити деструктор невбудованим, або реорганізувати програму. У прикладі вище інструкцію return у кожній мітці case можна замінити інструкцією break для того, щоб у функції була єдина точка виходу:
// переписано для забезпечення єдиної точка виходу
switch (swt) {
case 0:
break;
case 1:
// щось зробити
break;
case 2:
// зробити щось інше
break;
// і так далі
}
// єдина точка виходу
return;
Ми ще раз особливо відзначаємо принцип найменших привілеїв як один з найбільш фундаментальних принципів створення гарного програмного забезпечення. Розглянемо один зі способів застосування цього принципу до об'єктів.
Деякі об'єкти повинні допускати зміни, інші - ні. Програміст може використовувати ключове слово const для вказівки на те, що об'єкт незмінний - є константним і що будь-яка спроба змінити об'єкт є помилкою. Наприклад,
const Time noon (12, 0, 0);
об’являє як константний об'єкт noon класу Time і присвоює йому початкове значення 12 годин пополудні.
Компілятори C++ сприймають оголошення const настільки неухильно, що в підсумку не допускають ніяких викликів функцій-елементів константних об'єктів (деякі компілятори дають у цих випадках тільки попередження). Це жорстоко, оскільки клієнти об'єктів можливо захочуть використати різні функції-елементи читання "get", а вони, звичайно, не змінюють об'єкт. Щоб обійти це, програміст може оголосити константні функції-елементи; тільки вони можуть оперувати константними об'єктами. Звичайно, константні функції-елементи не можуть змінювати об'єкт - це не дозволить компілятор.
Константна функція вказується як const і в об’яві, і в описі за допомогою ключового слова const після списку параметрів функції, але перед лівою фігурною дужкою, що починає тіло функції. Наприклад, у наведеному нижче прикладі об’являється як константна функція-елемент деякого класу А
int A:: getValue () const {return privateDateMember};
яка просто повертає значення одного з даних-елементів об'єкта. Якщо константна функція-елемент описується поза об’явою класу, то як об’ява функції-елемента, так і її опис повинні включати const.
Тут виникає цікава проблема для конструкторів і деструкторів, які звичайно повинні змінювати об'єкт. Для конструкторів і деструкторів константних об'єктів оголошення const не потрібно. Конструктор повинен мати можливість змінювати об'єкт із метою присвоювання йому відповідних початкових значень. Деструктор повинен мати можливість виконувати підготовку завершення робіт перед знищенням об'єкта.
Програма на мал.4 створює константний об'єкт класу Time і намагається змінити об'єкт не константними функціями-елементами setHour, setMinute і setSecond. Як результат показані згенеровані компілятором Borland C++ попередження.
// TIME5. H
// Оголошення класу Time.
// Функції-елементи описані в TIMES. CPP
#ifndef TIME5_H idefine TIME5_H
class Time { public:
Time (int = 0, int = 0, int = 0); // конструктор за замовчуванням
// функції запису set
void setTime (int, int, int); // установкачасу
void setHour (int); // установкагодин
void setMinute (int); // установкахвилин
void setSecond (int); // установкасекунд
// функції читання get (звичайно об’являється const)
int getHour () const; // повертає значення годин
int getMinute () const; // повертає значення хвилин
int getSecondf) const; // повертає значення секунд
// функції друк (звичайно об’являється const)
void printMilitary () const; // друк військового часу void printStandard () const; // друк стандартного часу
private:
int hour; // 0-23
int minute; // 0-59
int second; // 0-59
};
#endif
// TIME5. CPP
// Опис функцій-елементів класу Time.
finclude <iostream. h>
iinclude "time5. h"
// Функція конструктор для ініціалізації закритих даних. // За замовчуванням значення рівні 0 (дивися опис класу). Time:: Time (int hr, int min, int sec) { setTime (hr, min, sec); }
// Встановка значень години, хвилин і секунд, void Time:: setTime (int h, int m, int s) {
hour = (h >= 0 && h < 24)? h: 0;
minute = (m >= 0 && m < 60)? m: 0;
second = (s >= 0 && s < 60)? s: 0; }
// Установка значення годин
void Time:: setHour (int h) { hour = (h >= 0 && h < 24)? h: 0; }
// Установка значення хвилин void Time:: setMinute (int m)
{ minute = (m >= 0 && m < 60)? m: 0; }
// Установка значення секунд void Time:: setSecond (int s)
{ second = (s >= 0 && s < 60)? s: 0; }
// Читання значення годин
int Time:: getHour () const { return hour; }
// Читання значення хвилин
int Time:: getMinute () const { return minute; }
// Читання значення секунд
int Time:: getSecond () const { return second; }
// Відображення часу у військовому форматі: HH: MM: SS
void Time:: printMilitary () const
{
cout " (hour < 10?"0": "")" hour " ": "
" (minute < 10?"0": "")" minute" ": "
" (second < 10?"0": "")" second; }
// Відображення часу в стандартному форматі: HH: MM: SS AM // (або РМ)
void Time:: printStandard () const {
cout " ( (hour == 12)? 12: hour% 12)" ": "
" (minute < 10?"0": "")" minute " ": " " (second < 10?"0": "")" second " (hour< 12?"AM": "PM"); }
// FIG7_1. CPP
// Спроба одержати доступ до константного об'єкта
// з не-константними функціями-елементами.
#include <iostream. h>
#include "time5. h"
main () {
const Time t (19, 33, 52); // константний об'єкт
t. setHour (12); // ПОМИЛКА: не-константна функція елемент t. setMinute (20); // ПОМИЛКА: не-константна функція елемент t. setSecond (39); // ПОМИЛКА: не-константна функція елемент
return 0; }
Compiling FIG7_1. CPP:
Warning FIG7_1. CPP: Non-const function
Time:: setHour (int) called for const object Warning FXG7 l. CPP: Non-const function
Time:: setMinute (int) callers for const object Warning FIG7 1. CPP: Non-const function
Time:: setSecond (int) called for const object
Мал.4. Використання класу Time з константними об'єктами й константними функціями-елементами
Зауваження: Константна функція-елемент може бути перевантажена неконстантним варіантом. Вибір того, яка з перевантажених функцій-елементів буде використатися, виконується компілятором автоматично залежно від того, був об’явлений об'єкт як const чи ні.
Константный об'єкт не може бути змінений за допомогою присвоювання, так що він повинен мати початкове значення. Якщо дані-елементи класу об’явлені як const, то треба використати ініціалізатор елементів, щоб забезпечити конструктор об'єкта цього класу початковими значенням даних-елементів. Мал.7 демонструє використання ініціалізатора елементів для завдання початкового значення константному елементу increment класу Increment. Конструктор для Increment змінюється в такий спосіб:
Increment:: Increment (int c, int i): increment (i) { count = c; }
Запис: increment (i) викликає завдання початкового значення елемента increment, рівного i. Якщо необхідно задати початкові значення відразу декільком елементам, просто включіть їх у список після двокрапки, розділяючи комами. Використовуючи ініціатори елементів, можна присвоїти початкові значення всім даним-елементам.
// Використання ініціалізатора елементів для
// ініціалізації даних константного вбудованого типу.
#include <iostream. h>
class Increment { public:
Increment (int з = 0, int i = 1);
void addlncrement () { count += increment; }
void print () const;
private:
int count;
const int increment; // константний елемент даних };
// Конструктор класу Increment Increment:: Increment (int c, int i)
: increment (i) // ініціали затор константного елемента