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

Int mainO

{

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

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

Obj = lnit(); // Ця настанова генерує помилку!!!!

Obj.ShowO; Ч Відображення "сміття".

getchO; return 0;

}

Результати виконання цієї програми мають такий вигляд:

Введіть рядок: Привіт Звільнення s-пам'яті.

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

Зверніть увагу на те, що виклик деструктора -strClassO відбувається тричі! Вперше він викликається при виході локального об'єкта obj з області видимості у момент його повернення з функції init(). Другий виклик деструктора ~strClass() B№ бувається тоді, коли руйнується тимчасовий об'єкт, який повертається функцією InitO- Коли функція повертає об'єкт, то автоматично створюється невидимий (для Вас) тимчасовий об'єкт, який зберігає повернуте значення. У нашому випадку цей об'єкт просто є побітовою копією об'єкта obj. Отже, після повернення з функції ви­користовується деструктор тимчасового об'єкта. Оскільки область пам'яті, що ви­діляється для зберігання рядка, який вводить користувач, вже була звільнена (при­чому двічі!), то під час виклику функції ShowO на екран буде виведено "сміття"1.

Нарешті, після завершення роботи коду програми викликається деструктор об'єк­та ОЬІ, який належить функції таіп(). Ситуація тут ускладнюється ще й тим, що під час першого виклику деструктора звільняється область пам'яті, виділена для збе­рігання рядка, отримуваного функцією ІпгїО. Таким чином, у цій ситуації погано не тільки те, що решта два звернення до деструктора -БШаБзО спробують звільнити вже звільнену динамічно виділену область пам'яті, але вони можуть зруйнувати систему динамічного розподілу пам'яті.

Тут важливо зрозуміти, що при поверненні об'єкта з функції для тимчасового об'єкта, який зберігає побітову копію об'єкта оЬІ, буде викликано його деструктор. Тому потрібно уникати повернення об'єктів у ситуаціях, коли це може мати згубні наслідки. Для вирішення цього питання замість повернення об'єкта з функції до­цільно використати повернення покажчика або посилання на об'єкт. Але здійсни­ти це не завжди вдається. Ще один спосіб вирішення цього питання полягає у ви­користанні конструктора копії, механізм реалізації якого розглянемо у наступно­му підрозділі.

  1. Механізми створення та використання конструктора копії

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

Основна причина використання конструктора копії полягає у тому, що при передачі об'єкта функції створюється побітова (тобто точна) його копія, яка пере­дається параметру цієї функції. Проте трапляються ситуації, коли така однакова копія об'єкта небажана. Наприклад, якщо оригінальний об'єкт містить покажчик на динамічно виділену область пам'яті, то і покажчик, який належить побітовій копії об'єкта, також посилатиметься на ту ж саму область пам'яті. Отже, якщо у копію об'єкта будуть внесені зміни у вміст цієї області пам'яті, то ці зміни стосу­ватимуться також оригінального об'єкта! Понад це, внаслідок завершення роботи функції, побітова копія об'єкта буде зруйнована за викликом деструктора, що не­гативно відобразиться на початковому об'єкті.

Аналогічна ситуація виникає при поверненні об'єкта з функції. Компілятор генерує тимчасовий об'єкт, який зберігає побітову копію об'єкта, що повертається функцією2. Цей тимчасовий об'єкт виходить за межі області видимості функції відразу ж, як тільки ініціатору виклику цієї функції буде повернуте "обіцяне" зна­чення, після чого негайно викликається деструктор тимчасового об'єкта. Оскільки цей деструктор руйнує область пам'яті, потрібну для виконання далі коду програ­ми, то наслідки її роботи будуть невтішні.

Основна причина виникнення цієї проблеми полягає у створенні побітової копії об'єкта. Щоб запобігти їй, необхідно точно визначити, що повинно відбува­

тися, коли створюється така копія об'єкта, і, тим самим, уникнути небажаних по­бічних ефектів. Цього можна домогтися шляхом створення конструктора копії об'єкта.

Найпоширеніший формат конструктора копії об'єкта має такий вигляд:

ім'я_класу (const ім'я_класу &obj]

{

// Тіло конструктора копії

}

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

У мові програмування C++ визначено дві ситуації, у яких значення одного об'єкта передається іншому: при присвоєнні та ініціалізації. Ініціалізація об'єкта може виконуватися трьома способами, тобто у випадках, коли:

  • один об'єкт безпосередньо ініціалізує інший об'єкт, як, наприклад, в оголошенні;

  • копія об'єкта передається як аргумент параметру функції;

  • генерується тимчасовий об'єкт (найчастіше як значення, що повертається функцією).

Наприклад, нехай завчасно створено об'єкт ObjY типу myClass. Тоді у процесі

виконання таких подальших настанов буде викликано конструктор копії класу myClass:

myClass ObjX = ObjY; // Об'єкт ObjY безпосередньо ініціалізує об'єкт ObjX.

Fun1 (ObjY); // Об'єкт ObjY передається як аргумент

ObjY = Fun2(); // Об'єкт ObjY приймає об'єкт, що повертається функцією.

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

Bapmoa нати! Конструктори копії не роблять ніякого впливу на операції

присвоєння.

Щоб глибше зрозуміти призначення конструктора копії, розглянемо Грунтов­ніше його значення у кожній з цих трьох ситуацій.