Конструкторы и деструкторы : инициализация объектов

Конструкторы и деструкторы

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

Конструктор — функция, предназначенная для инициализации объектов класса.

Рассмотрим класс date:

 
 
 
 
 
 
class date 
{
  int day, month, year;
public:
  set(intintint);
};

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

ООП дает возможность программисту описать функцию, явно предназначенную для инициализации объектов. Поскольку такая функция конструирует значения данного типа, она называется конструктором.

Конструктор всегда имеет то же имя, что и сам класс и никогда не имеет возвращаемого значения. Когда класс имеет конструктор, все объекты этого класса будут проинициализированы.

 
 
 
 
 
class date {
  int day, month, year;
public:
  date(intintint); // конструктор
};

Если конструктор требует аргументы, их следует указать:

 
 
 
date today = date(6,4,2014); // полная форма
date xmas(25,12,0); // сокращенная форма
// date my_burthday; // недопустимо, опущена инициализация

Если необходимо обеспечить несколько способов инициализации объектов класса, задается несколько перегруженных конструкторов с разным набором аргументов:

 
 
 
 
 
 
 
class date {
  int month, day, year;
public:
  date(intintint); // день месяц год
  date(char*); // дата в строковом представлении
  date(); // дата по умолчанию: сегодня
};

Конструкторы подчиняются тем же правилам относительно типов параметров, что и перегруженные функции.

Если конструкторы различаются по типам своих параметров, то компилятор при каждом использовании должен выбрать соответствующий:

 
 
 
date july4("Февраль 27, 2014");
date guy(27, 2, 2014);
date now; // инициализируется по умолчанию

Одним из способов сократить количество перегруженных функций (в том числе и конструкторов) является использование значений по умолчанию.

Конструктор по умолчанию

Конструктор, не требующий параметров, называется конструктором по умолчанию. Это может быть конструктор с пустым списком параметров или конструктор, в котором все аргументы имеют значения по умолчанию.

Конструкторы могут быть перегруженными, но конструктор по умолчанию может быть только один.

 
 
 
 
 
 
 
 
class date 
{
  int month, day, year;
public:
  date(intintint);
  date(char*);
  date(); // конструктор по умолчанию
};

При создании объекта вызывается конструктор, за исключением случая, когда объект создается как копия другого объекта этого же класса, например:

 
date date2 = date1;

Однако имеются случаи, в которых создание объекта без вызова конструктора осуществляется неявно:

  • формальный параметр – объект, передаваемый по значению, создается в стеке в момент вызова функции и инициализируется копией фактического параметра;
  • результат функции – объект, передаваемый по значению, в момент выполнения оператора return копируется во временный объект, сохраняющий результат функции.

Во всех этих случаях транслятор не вызывает конструктора для вновь создаваемого объекта:

  • date2 в приведенном определении;
  • для создаваемого в стеке формального параметра;
  • для временного объекта, сохраняющего значение, возвращаемое функцией.

Вместо этого в них копируется содержимое объекта-источника:

  • date1 в приведенном примере;
  • фактического параметра;
  • объекта-результата в операторе return.

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

Как правило, при создании нового объекта на базе уже существующего происходит поверхностное копирование, то есть копируются те данные, которые содержит объект-источник.

При этом если в объекте-источнике имеются указатели на динамические переменные и массивы, или ссылки, то создание копии объекта требует обязательного дублирования этих объектов во вновь создаваемом объекте.

С этой целью вводится конструктор копии, который автоматически вызывается во всех перечисленных случаях. Он имеет единственный параметр — ссылку на объект-источник.

В качестве примера рассмотрим класс строки String, содержащий указатель на строку и длину строки. Класс содержит конструктор по умолчанию и методы

  • set() — для установки нового значения строки;
  • out() — для вывода значения строки.

Проблема

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <stdlib.h>
using namespace std;
class String
{
  char* str = 0;
  int size = 0;
public:
  String(char* arg);  // Конструктор по умолчанию (аргумент задан по умолчанию)
  char* out() { return str; };
  void set(char* arg);   // установка нового значения строки
};
void String::set(char* arg)
{
  if (str != 0)    // освобождаем память если в строке что-то было
    delete[] str;
  size = strlen(arg) + 1;
  str = new char[size];
  strcpy(str, arg);
}
String::String(char* arg = (char*)""// конструктор по умолчанию
{
  set(arg);
}
int main()
{
  system("chcp 1251");
  system("cls");
  cout << "До изменения" << endl;
  String s = (char*)"abc";  
  String p = s;
  cout << "s: " << s.out() << endl;
  cout << "p: " << p.out() << endl;
  s.set((char*)"rty");
  cout << "После изменения" << endl;
  cout << "s: " << s.out() << endl;
  cout << "p: " << p.out() << endl;
}

Результат выполнения
Копирование строки без конструктора копии

В результате того, что копирование строки (строка кода 32) было выполнено поверхностно, изменение поля str внутри объекта s приводит к разрушению поля str объекта p, поскольку функция set() перед переопределением строки освобождает память.

Решением этой проблемы станет использование конструктора копии.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <stdlib.h>
using namespace std;
class String
{
  char* str = 0;
  int size = 0;
public:
  String(char* arg);  // Конструктор по умолчанию (аргумент задан по умолчанию)
  String(String&);  // Конструктор копии
  char* out() { return str; };
  void set(char* arg);   // установка нового значения строки
};
void String::set(char* arg)
{
  if (str != 0)    // освобождаем память если в строке что-то было
    delete[] str;
  size = strlen(arg) + 1;
  str = new char[size];
  strcpy(str, arg);
}
String::String(char* arg = (char*)"")  
{
  set(arg);
}
String::String(String& right)  // тело конструктора копии

  set(right.str);
}
int main()
{
  system("chcp 1251");
  system("cls");
  cout << "До изменения" << endl;
  String s = (char*)"abc";  
  String p = s;
  cout << "s: " << s.out() << endl;
  cout << "p: " << p.out() << endl;
  s.set((char*)"rty");
  cout << "После изменения" << endl;
  cout << "s: " << s.out() << endl;
  cout << "p: " << p.out() << endl;
}

В коде функции main() ничего не поменялось.

В описание класса добавился конструктор копии.

Результат выполнения

Копирование строки с конструктором копии

Деструкторы

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

Деструктор обеспечивает соответствующую очистку объектов указанного типа. Имя деструктора представляет собой имя класса с предшествующим ему знаком «тильда» ~.

Так, для класса X деструктор будет иметь имя ~X().

Многие классы используют динамическую память, которая выделяется конструктором, а освобождается деструктором.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class date 
{
  int day, year;
  char *month;
public:
  date(int d, char* m, int y) 
  {
    day = d;
    month = new char[strlen(m)+1];
    strcpy_s(month, strlen(m)+1,m);
    year = y;
  }
  ~date() { delete[] month; } // деструктор
};

Поля, имеющие тип класса

Пусть имеется класс vect, реализующий защищенный массив, и необходимо хранить несколько значений для каждого такого массива: возраст, вес и рост группы лиц. Группируем 3 массива внутри нового класса.

Конструктор нового класса имеет пустое тело и список вызываемых конструкторов класса vect, перечисленных после двоеточия : через запятую ,. Они выполняются с целым аргументом i, создавая 3 объекта класса vect: a, b, c.

Конструкторы членов класса всегда выполняются до конструктора класса, в котором эти члены описаны. Порядок выполнения конструкторов для членов класса определяется порядком объявления членов класса. Если конструктору члена класса требуются аргументы, этот член с нужными аргументами указывается в списке инициализации.

Деструкторы вызываются в обратном порядке.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <iostream>
using namespace std;
class vect
{
  int size;
  int *array;
public:
  vect(int size) 
  {
    this->size = size;
    array = new int;
    for (int i = 0; i < size; i++)
      array = 0;
  }
  int& element(int i) { return array; }
  int getSize() { return size; }
};
class multi_v
{
public:
  vect a;
  vect b;
  vect c;
  multi_v(int size): a(size), b(size), c(size) { }
  int getSize() { return a.getSize(); }
};
int main()
{
  system("chcp 1251");
  system("cls");
  multi_v f(3);
  for (int i = 0; i <= f.getSize(); i++)
  {
    f.a.element(i) = 10 + i;
    f.b.element(i) = 20 + 5 * i;
    f.c.element(i) = 120 + 5 * i;
  }
  for (int i = 0; i <= f.getSize(); i++)
  {
    cout << f.a.element(i) << "лет \t";
    cout << f.b.element(i) << "кг \t";
    cout << f.c.element(i) << "см" << endl;
  }
  cin.get();
  return 0;
}

При выполнении программы перед выходом из блока main для каждого члена vect будет вызываться индивидуальный деструктор.

Результат работы программы

Прокрутить вверх