Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Не підтверджено.doc
Скачиваний:
0
Добавлен:
01.04.2025
Размер:
3 Мб
Скачать
☆
  1. Особливості реалізації оператора присвоєння

У попередньому розділі ми розглянули потенційну проблему, пов'язану з пе­редачею об'єктів функціям і поверненням їх з них. У обох випадках проблема ви­никла під час використання конструктора за замовчуванням, який створює побіто- ву копію об'єкта. Згадаймо (див. роз. 3.7), раніше це питання ми вирішували шля­хом створення власного конструктора копії, який точно визначає, як повинна бути створена копія об'єкта.

Подібна проблема може виникати і під час присвоєння одного об'єкта іншо­му. За замовчуванням об'єкт, який знаходиться з лівого боку від операції прис­воєння "=", отримує побітову копію об'єкта, який знаходиться справа. До негатив­них наслідків це може призвести у випадках, коли при створенні об'єкт виділяє певний ресурс (наприклад, пам'ять), а потім змінює його або звільняє. Якщо після виконання операції присвоєння об'єкт змінює або звільняє цей ресурс, то другий об'єкт також змінюється, оскільки він все ще використовує його ресурс. Вирішу­вати це питання можна також шляхом перевизначення оператора присвоєння.

Щоб до кінця зрозуміти суть описаної вище проблеми, розглянемо таку (не­коректну) програму.

Код програми 4.7. Демонстрація механізму появи помилки, яка виникає при поверненні об'єкта з функції

#include <vcl>

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

#include <conio> // Для консольного режиму роботи

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

class strClass { // Оголошення класового типу char *s; public:

strClassO {s = 0;}

strClass(const strClass &obj); // Оголошення конструктора копії ~strClass() {if(s) deleters; cout «"Звільнення s-пам'яті" « endl;} void Show(char*c) {cout« c « s « endl;} void Set(char*str);

};

// Визначення конструктора копії. strClass::strClass(const strClass &obj)

{

s = new char[strlen(obj.s)+1]; strcpy(s, obj.s);

}

// Завантаження рядка, void strClass::Set(char*str)

{

s = new char[strlen(str)+1]; strcpy(s, str);

}

// Ця функція повертає об'єкт типу strClass. strClass InitO {

char str[80]; strClass obj;

cout«"Введіть рядок:"; сіп » str;

obj.Set(str);

return obj;

}

Int mainO

{

strClass Obj; // Створення об'єкта класу

// Присвоюємо об'єкт, повернутий функцією InitO, об'єкту Obj.

Obj = InitO; Ч Ця настанова генерує помилку!!!!

Obj.Show("s="); getchO; return 0;

}

Можливі результати виконання цієї програми мають такий вигляд: Введіть рядок: Привіт Звільнення s-пам'яті.

Звільнення s-пам'яті. s= тут ''сміття''

Звільнення s-пам'яті.

Залежно від використовуваного компілятора, на екрані монітора Ви можете побачити переважно "сміття" або й ні. Програма може також згенерувати помилку тривалості її виконання. У будь-якому випадку помилки не минути. І ось чому.

У цьому коді програми конструктор копії коректно обробляє повернення об'­єкта функцією lnit(). Згадаймо, у разі, коли функція повертає об'єкт, то для збері­гання повернутого нею значення створюється тимчасовий об'єкт. Оскільки при створенні об'єкта-копії конструктор копії виділяє нову область пам'яті, то член-да- них s початкового об'єкта і член-даних s об'єкта-копії вказуватимуть на різні об­ласті пам'яті, які, як наслідок, не стануть псувати один одного.

Проте помилки не минути, якщо повернутий функцією об'єкт присвоюється об'єкту Obj, оскільки у процесі виконання операції присвоєння за замовчуванням створюється побітова його копія. У цьому випадку тимчасовий об'єкт, який повер­тається функцією InitO, копіюється в об'єкт Obj. Як наслідок, член obj.s вказує на ту ж саму область пам'яті, що і член s тимчасового об'єкта. Але після виконання опе­рації присвоєння в процесі руйнування тимчасового об'єкта ця пам'ять звіль­няється. Отже, член obj.s тепер вказуватиме на вже звільнену пам'ять! Окрім цього, пам'ять, яка адресується членом obj.s, повинна бути звільнена і після завершення роботи коду програми, тобто удруге. Щоб запобігти цьому, необхідно перевизна- чити оператор присвоєння так, щоб об'єкт, який розташовується зліва від операто­ра присвоєння, виділяв власну область пам'яті.

Реалізацію цього рішення покажемо у такій відкоректованій програмі.

Код програми 4.8. Демонстрація механізму появи помилки, яка може виникну­ти при поверненні об'єкта з функції

#include <vcl>

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

#include <conio> // Для консольного режиму роботи

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

class strClass { // Оголошення класового типу char *s; public:

strClass(); // Оголошення звичайного конструктора strClass(const strClass &obj); // Оголошення конструктора копії -strClassO {if(s) deleters; cout«"Звільнення s-пам'яті"« endl;} void Show(char*c) {cout« c « s « endl;} void Set(char*str);

// Перевизначений оператор присвоєння strClass operator=(const strClass &obj);

};

// Визначення звичайного конструктора. strClass::strClass()

{

s = new char ('\0'); // Член s вказує на NULL-рядок.

}

// Визначення конструктора копії. strClass::strClass(const strClass &obj)

s = new char[strlen(obj.s)+1]; strcpy(s, obj.s);

}

// Завантаження рядка, void strClass::Set(char*str)

{

s = new char[strlen(str)+1]; strcpy(s, str);

}

// Перевизначення оператора присвоєння "=". strClass strClass::operator=(const strClass &obj)

{

/* Якщо виділена область пам'яті має недостатній розмір, виділяється нова область пам'яті. */ if(strlen(obj.s) > strlen(s)) { delete[]s;

s = new char[strlen(obj.s)+1];

}

strcpy(s, obj.s);

// Повернення модифікованого об'єкта операнда, адресованого покажчиком return *this;

}

// Ця функція повертає об'єкт типу strClass. strClass InitO {

strClass obj; char str[80];

cout«"Введіть рядок:"; сіп » str; obj.Set(str);

return obj;

}