Присвоєння і Ініціалізація h2>
Розглянемо дуже простий клас строк string: p>
struct string ( p>
char * p; p>
int size;// розмір вектора, на який вказує p p>
string (int sz) (p = new char [size = sz];) p>
~ string () (delete p;) p>
); p>
Рядок - це структура даних, що складається з вектора
символів і довжини цього вектора. Вектор створюється конструктором і знищується
деструкторами. Однак це може призвести до неприємностей. p>
Наприклад:
p>
void f () p>
( p>
string s1 (10); p>
string s2 (20); p>
s1 = s2; p>
) p>
буде розміщувати два вектори символів, а присвоювання
s1 = s2 буде псувати вказівник на один з них і дублювати інший. На виході з
f () для s1 і s2 викликатиметься деструктор і знищувати один і той же вектор з
непередбачувано руйнівними наслідками. Вирішення цієї проблеми полягає в
тому, щоб відповідним чином визначити присвоювання об'єктів типу
string: p>
struct string ( p>
char * p; p>
int size;// розмір вектора, на який вказує p p>
string (int sz) (p = new char [size = sz];) p>
~ string () (
delete p;) p>
void
operator = (string &) p>
); p>
void string:: operator = (string & a) p>
( p>
if (this ==
& a) return;// остерігатися s = s; p>
delete p; p>
p = new
char [size = a.size]; p>
strcpy (p, a.p); p>
) p>
Це визначення string гарантує, і що попередній
приклад буде працювати як передбачалося. Однак невелика зміна f ()
призведе до появи тієї ж проблеми в новому вигляді: p>
void f () p>
( p>
string s1 (10); p>
s2 = s1; p>
) p>
Тепер створюється тільки один рядок, а знищується два.
До неініціалізовані об'єкту певна користувачем операція
присвоювання не застосовується. Швидкий погляд на string:: operator = () пояснює,
чому було нерозумно так робити: вказівник p міститиме невизначене і
абсолютно випадкове значення. Часто операція привласнення покладається на те,
що її аргументи ініціалізований. Для такої ініціалізації, як тут, це не
так за визначенням. Отже, потрібно визначити схожу, але іншу,
функцію, щоб обробляти ініціалізацію: p>
struct string ( p>
char * p; p>
int size;// розмір вектора, на який вказує p p>
string (int sz) (p = new char [size = sz];) p>
~ string () (
delete p;) p>
void
operator = (string &) p>
string (string &); p>
); p>
void string:: string (string & a) p>
( p>
p = new
char [size = a.size]; p>
strcpy (p, a.p); p>
) p>
Для типу X ініціалізацію тим же типом X обробляє
конструктор X (X &). Не можна не підкреслити ще раз, що присвоювання і
ініціалізація - різні дії. Це особливо важливо при описі
деструктора. Якщо клас X має конструктор, що виконує нетривіальну роботу
на зразок звільнення пам'яті, то швидше за все буде потрібно повний комплект функцій,
щоб повністю уникнути побітового копіювання об'єктів: p>
class X ( p>
// ... p>
X (something);// конструктор:
створює об'єкт p>
X (& X);// конструктор: копіює в
ініціалізації p>
operator = (X &);// присвоювання: чистить і копіює p>
~ X ();// деструктор: чистить p>
); p>
Є ще два випадки, коли об'єкт копіюється: як
параметр функції і як повертається значення. Коли передається параметр,
ініціалізується неініціалізовані до цього мінлива - формальний
параметр. Семантика ідентична семантиці ініціалізації. Те ж саме відбувається
при поверненні з функції, хоча це менш очевидно. В обох випадках буде
застосований X (X &), якщо він визначений: p>
string g (string arg) p>
( p>
return arg; p>
) p>
main () p>
( p>
string s =
"asdf"; p>
s = g (s); p>
) p>
Ясно, що після виклику g () значення s зобов'язане бути
"asdf". Копіювання значення s в параметр arg складності не
представляє: для цього треба кликати string (string &). Для взяття копії
цього значення з g () потрібно ще один виклик string (string &); на цей раз
ініціалізіруемой є тимчасова змінна, яка потім присвоюється s.
Такі змінні, природно, знищуються як годиться за допомогою string:: ~ string ()
при першій нагоді. p>
Список літератури h2>
Для підготовки даної роботи були використані матеріали
з сайту http://www.realcoding.net
p>