Класи: копіювання і присвоювання. Частина вступна (исходники), Різне, Програмування, статті

Елементи класу, про які завжди необхідно пам’ятати


Мова піде про чотири дуже спеціальних членах, які повинні враховуватися при створенні кожного класу. Це конструктор за умовчанням, конструктор копій, операція присвоювання і деструктор. Зауважте, це зовсім не означає, що ви повинні обов’язково кожен раз писати всі ці чотири функції для кожного класу, який ви створюєте. Мова йде тільки про те, що ви повинні приймати їх до уваги при створенні будь-якого класу і робити висновки про їх необхідність. Ці чотири члена в принципі є функціями, але не типовими. Вони виглядають зовсім як функції, але, як нескладно помітити, деякі з них не повертають ніяких значень. Ці функції призначені для створення (ініціалізації), копіювання та видалення (руйнування) об’єктів класу.

Дуже докладно ми розглянемо проблеми копіювання і присвоювання для класів, присвятивши цьому кілька окремих матеріалів. А поки, все, що викладається нижче можна розглядати, як вступну частину до цих досить складним питанням.

Конструктори


Конструктори – це члени класів, які використовуються для створення об’єктів-екземплярів класів. Є кілька різновидів конструкторів, в їх числі є досить своєрідні, але основне їхнє призначення в будь-якому випадку одне й те саме: забезпечення зручного способу створення об’єкта-екземпляра класу.

Ми розглянемо конструктор за умовчанням, конструктор копій, аргументи за умовчанням в конструкторі і інші конструктори.

Як уже було відзначено, конструктори – це функції-члени, не повертають ніяких значень (навіть типу void). Інший їх особливістю є те, що їх ім’я має в точності, включаючи регістр символів, збігатися з ім’ям класу. Тобто якщо клас називається Any_Class, то його конструктор також повинен називатися Any_Class.

Конструктор за умовчанням


Отже, конструктор за замовчуванням (default constructor) – це конструктор, не приймає (не має) аргументів. Таким чином, конструктор за замовчуванням для якогось довільного класу буде виглядати так:

Код:






class ANY_CLASS
{
 public: ANY_CLASS (); / / конструктор за замовчуванням … / / Тут все інше
};

Зазвичай конструктори оголошуються у відкритій (public) секції класу, оскільки діяльність конструкторів полягає у створенні об’єкта типу класу і вони викликаються ззовні класу. Виклики конструкторів, як правило, відбуваються неявно. Наприклад, створення одиночного об’єкта типу ANY_CLASS може виглядати наступним чином:

Код:






ANY_CLASS ас; / / ас – це об’єкт класу ANY_CLASS

Зауважте, що в цьому операторі зовсім відсутні дужки, конструювання – це неявна операція.

Масив об’єктів типу ANY_CLASS може бути створений так:

Код:






ANY_CLASS aac [10]; / / aас це масив з 10 елементів

Як бачите, синтаксис оголошення масиву об’єктів точно такий же, як і синтаксис оголошення статичного масиву даних будь-якого базового (вбудованого) типу. Одне із завдань мови C + + полягає в наданні користувачам можливості звертатися зі складними типами даних таким же чином, як і зі вбудованими. Завдяки неявній природі конструювання об’єктів досягається перший її аспект: створення об’єкта виглядає точно так само, як і створення звичайної змінної.

До речі, створити МАСИВ об’єктів можна ТІЛЬКИ в тому випадку, якщо для класу визначено конструктор за умовчанням.


Конструктор копій


Робота конструктора копій (copy constructor) полягає в наданні можливості ініціалізації (створення) нового об’єкта з вже існуючого. Для пояснення глибинних механізмів цього процесу потрібна б не одна голова, тому ми ознайомимося з ними поки коротенько. Загальний синтаксис конструктора копій такий:

Код:






My_Class (const My_Class &); / / тут My_Class це ім’я класу

У конструкторі копіювання класу My_Class як параметр використовується посилання на об’єкт цього класу. Причому це посилання оголошується з специфікатором const. І в цьому немає нічого дивного. Як відомо, вираження виклику функції з параметром типу X нічим не відрізняється від виразу виклику функції, у якій параметром є посилання на об’єкт типу X. При виклику такої функції не доводиться копіювати об’єкти як параметри. Передача адреси не вимагає копіювання об’єкта, а значить, при цьому не буде і рекурсії.

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

Тут є багато моментів, які слід мати на увазі, і всі вони виключно важливі для розуміння класів C + +, але подальше обговорення конструктора копій ми відкладемо до глави, спеціально присвяченій копіюванню та присвоєння.


Аргументи за замовчуванням в конструкторі


У будь-яких функцій можуть бути аргументи за умовчанням. Це відноситься до конструкторам в тій же мірі, як і до будь-яких інших функцій-членам і глобальним функціям. Аргумент за замовчуванням – це значення, яке присвоюється аргументу, якщо користувач явно не задав інше значення. Оскільки зараз мова йде конкретно про конструкторів, розглянемо відповідний приклад. Аргументи за замовчуванням зручні в тих випадках, коли відомо певний (або переважне) значення аргументу, але при цьому бажано зберегти можливість завдання іншого значення при створенні об’єкта. Розглянемо як приклад якийсь гіпотетичний клас, що описує файл.

Код:






class FILE
{
public: FILE (char * FileName = “file.bin”); / / “file.bin” це аргумент за / / Замовчуванням … / / Все інше
};

Якщо аргументу FileName типу char * в конструкторі не передати яке або значення, то буде автоматично підставлено значення “file.bin”. Таким чином, екземпляри класу FILE можна створювати наступними способами:

Код:






FILE IniFile; / / буде створений файл з ім’ям file.bin FILE Archive (Archive.dat); / / буде створений файл з ім’ям Archive.dat

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

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

Код:






DATA (int а = 0, int b); / / явна помилка: DATA F (, 5) виглядає безглуздо … DATA (int а, int b = 10); / / правильно, можна створити об’єкти DATA G (5); / / Або DATA G (3, 4); DATA (int a = 0, int b = 10) ;/ / правильно, можна створити об’єкти DATA Н (3, 4); / / Або DATA R;

Правило для аргументів за замовчуванням було введено для того, щоб не виникало ситуацій типу “пробіл кома аргумент” (див. перший приклад для об’єкта F (, 5)), які досить чреваті помилками, та й виглядають неважливо.

Необхідно також відзначити наступне: конструктор, всі аргументи якого забезпечені значеннями за замовчуванням, може викликатися і з аргументами, і без аргументів, тобто при виклику виглядати як звичайний конструктор за замовчуванням (см приклад для DATA Н (3, 4); та DATA R ;). Тому бажано уникати невизначеності, що виникає при одночасному завданні в класі конструктора за замовчуванням, тобто без аргументів, і конструктора, у якого всі аргументи мають значення за замовчуванням.

Конструктори в цілому


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

Вимог до створення конструкторів небагато: ви змушені враховувати, що конструктор викликається неявно, що він не має значення, що повертається і що його ім’я в точності повинно співпадати з ім’ям його класу.

Деструктори


Деструктори виконують роботу, зворотну тій, що проробляють конструктори. Хоча клас може мати кілька конструкторів, але деструктор може бути тільки один.

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

Код:






class ANY_CLASS
{
 public: ANY_CLASS (); / / конструктор за замовчуванням ANY_CLASS (int d); / / ще один конструктор ~ ANY_CLASS (); / / а це – деструктор
};

Деструктор майже завжди викликається неявно. Виклик деструктора відбувається або при виході об’єкта за межі своєї області видимості, або при знищенні динамічного створеного (операцією new) об’єкта операцією delete.

Віртуальний деструктор


Якщо клас може мати спадкоємців, то краще використовувати віртуальний деструктор. Синтаксис віртуального деструктора точно такий же, як і у будь-якого іншого деструктора, за винятком того, що його оголошення починається з ключового слова virtual.

Код:






class ANY_CLASS
{
 public: ANY_CLASS (); / / конструктор за замовчуванням vrtual ~ ANY_CLASS (); / / а це віртуальний деструктор
};

Оголошення деструктора віртуальним не відіб’ється на продуктивності. так що має сенс завжди робити його віртуальним, якщо немає дуже вагомих причин утриматися від цього. Відмова від застосування віртуальних деструкторів може привести до витоку пам’яті в похідних класах, але це тема для окремої розмови. До неї ми повернемося більш докладно пізніше.

Наведений нижче приклад придуманий виключно в цілях демонстрації неявних викликів конструктора і деструктора при створенні екземпляра класу. При виклику конструктора або деструктора програма просто друкує відповідне повідомлення.

Код:






include <iostream.h> / / Цей клас просто демонструє неявні виклики / / Конструктора і деструктора

class DEMO
{
 public:
  DEMO()   { cout  “constructor”  endl; }
  virtual ~DEMO()   { cout  “destructor”  endl; }
};

void main(void)
{ DEMO staticDemo; / / статичне розміщення, деструктор викликається при / / Виході за межі області видимості
 DEMO *dynamicDemo = new DEMO; / / Динамічне розміщення, / / Деструктор викликається при знищенні об’єкта
 delete dynamicDemo;
}


У цьому прикладі визначається клас, який не містить нічого, крім відкритих (public) деструктора і конструктора. Обидві ці функції оголошені і визначені в класі. При створенні об’єкта неявно викликається конструктор і друкується слово “constructor”, а при виклику деструктора, відповідно, слово “destructor”.

Усередині функції main () створюються два об’єкти, один статичний, в стеку, а другий в купі (heap) – динамічний. В результаті виконання цього прикладу на екран буде виведено наступне:

Код:






constructor
constructor
destructor
destructor

Перший рядок виводиться конструктором при створенні об’єкта staticDemo. Другий рядок виводиться конструктором при створенні об’єкта dynamicDemo. Третій рядок результат виклику деструктора об’єкта dynamicDemo при його знищенні оператором delete. Четверта рядок результат виклику деструктора об’єкта staticDemo. Деструктор статичного об’єкта був викликаний останнім, при виході об’єкта з області видимості – в тій точці, де розташована закриває дужка функції main ().

Операція присвоювання


Завдання операції присвоювання для класу полягає в тому, щоб дати вам можливість зробити один об’єкт еквівалентним іншому. Сама операція представляє з себе знак рівності (=). Присвоєння настільки важливо, що якщо ви самі не реалізуєте його, компілятор зробить це за вас.

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

На жаль, це вдається не завжди. Можливі ситуації, коли ініціалізація або копіювання нових об’єктів з інших об’єктів просто неприпустимі; для вказівки цього існують спеціальні способи. Поки що, до з’ясування подробиць, ми утримаємося від розгляду прикладів неправильного застосування функцій копіювання та привласнення.

Покажчик на самого себе: this


Наостанок розглянемо ще одне поняття покажчик на самого себе, оскільки в подальшому матеріалі ми будемо використовувати це поняття.

У кожного об’єкта є фізичне місце розташування в пам’яті (адреса). Оперувати з цими розташуванням можна за допомогою покажчиків. Як правило, в покажчиках зберігаються адреси. Звертаючись до покажчика, ми в дійсності звертаємося до об’єкту, розташованого по міститься в цьому покажчику адресою.

Кожен об’єкт класу має свій фізичний адреса і його можна витягти з покажчика this. Це внутрішній (як би прихований від прямого погляду) покажчик, який є у кожного класу. Насправді ми завжди неявно використовуємо покажчик this, коли звертаємося до членів всередині області видимості функції-члена класу. Ось приклад:

Код:






сlass THIS_DEMO
{
  public: THIS_DEMO () {this-> a = 5;} / / зазвичай ми просто пишемо а = 5; … / / Все інше
  private:
    int a;
};

Є клас, в якому описаний конструктор і член класу int a в закритій секції (private) класу. У конструкторі THIS_DEMO міститься присвоювання цілочисельного члену а значення 5. Зазвичай this явно не вказується, просто пишуть а = 5;. Але в даному випадку ми демонструємо, те, що завжди неявно відбувається використання this всередині функцій-членів класу при зверненні до членів класу.

Звичайно, немає потреби використовувати цей покажчик для звернення до членів класу зсередини класу, але це єдиний засіб послатися з об’єкта на об’єкт в цілому. Покажчик this іноді буває вкрай корисний.

Ну от, на цей раз все. Наступний матеріал буде розбитий на кілька частин і присвячений проблемам копіювання та привласнення в класах.

Продовження. Частина 1

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*