ВВЕДЕНИЕ

2.6 Понятие конструкторов и деструкторов

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

Когда определяется объект, например,

Simple_Counter my_count;

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

При создании объектов может использоваться один из трех вариантов:

· создание объектов с инициализацией по умолчанию;

· создание объектов со специальной инициализацией;

· создание объектов путем копирования других объектов.

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

Объявление класса, дополненное конструкторами, может иметь следующий вид:

class Simple_Counter {

int count;

public: // конструктор без параметров

Simple_Counter() { count = 0; }

// конструктор с параметром

Simple_Counter(int beg_val) { count = beg_val; }

// конструктор копирования

Simple_Counter(Simple_Counter & old_obj) { count = old_obj.count; }

void SetCount(int val) { count = val; }

int GetCount() { return count; }

};

void main() {

// вызывается конструктор без параметров

Simple_Counter my_count;

// ...

// вызывается конструктор с параметром

Simple_Counter * pmy_count = new Simple_Counter(66);

// ...

// вызывается конструктор копирования

Simple_Counter your_count = Simple_Counter(my_counter);

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

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

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

Вопросы для самопроверки.

1. Объясните назначение конструкторов и деструкторов.

2. Могут ли члены-данные иметь атрибут const? Как инициализировать эти члены?

3. Можно ли создавать объекты массивов? Какие конструкторы вызываются при их создании? Как вызываются деструкторы?

4. Как создать динамический массив объектов? Как освободить память при уничтожении динамического массива объектов?

5. Пусть класс имеет вид:

class Vector {

int * data;

int size;

public:
Vector(int)
{ data = new T [s];

size = s; }

~Vector() { delete data; } };

Что будет происходить при выполнении следующих действий?

void main() {

Vector a(50);

Vector * pa = &a;

Vector b = a;

}

Как избавиться от ошибок в программе?

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

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

С процессом создания объектов связано понятие их инициализации. Инициализировать объекты обычным способом нельзя. Их инициализация осуществляется либо явным присваиванием (копированием) другого объекта, либо неявным вызовом конструктора. Если конструктор имеет формальные параметры, то в определении переменной после ее имени должны присутствовать в скобках значения фактических параметров.

Момент вызова конструктора и деструктора определяется временем создания и уничтожения объектов:

- для статических объектов - конструктор вызывается перед входом в main(), деструктор - после выхода из main(). Конструкторы вызываются в порядке опредлеления объектов, деструкторы – в обратном порядке;

- для автоматических объектов - конструктор вызывается при входе в функцию (блок), деструктор - при выходе из него;

- для динамических объектов - конструктор вызывается при выполнении оператора new, деструктор - при выполнении оператора delete.

В Си++ возможно определение массива объектов класса. При этом конструктор и деструктор вызываются для каждого элемента массива и не должны иметь параметров. При выполнении оператора delete для ссылки на массив объектов необходимо также указывать его размерность. Конструктор для массива объектов должен быть без параметров.

#include <stdio.h>

#include <dos.h>

static int days[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };

class dat {

int day,month,year;

public:

dat(int,int,int); // Конструктор с параметрами (возможно умолчание)

dat(char *); // Конструктор с одним параметром

dat(); // Конструктор без параметров

~dat(); // Деструктор

};

//------- Конструктор с параметром - текстовая строка ---------

dat::dat(char *s)

{

int i;

char ss[80];

strcpy(ss,s);

for (i=0; ss[i] !=0; i++)

if (ss[i]=='-') ss[i]=','; // Замена '-' на ','

sscanf(ss,"%d%d%d",&day,&month,&year);

}

// Конструктор с тремя параметрами (по умолчанию 0 - текущая дата)

dat::dat(int d=0, int m=0, int y=0) {

struct date x;

getdate(&x); // Стандартная функция получения текущей даты

// Проверка на значение по умолчанию

year = (y == 0) ? x.da_year : y;

month= (m == 0) ? x.da_month: m;

day = (d == 0) ? x.da_day : d;

}

//------ Конструктор без параметров --------------------------

dat::dat()

{

struct date x;

getdate(&x); // Стандартная функция получения текущей даты

year = x.da_year ;

month= x.da_month;

day = x.da_day;

}

//------ Деструктор ------------------------------------------

dat::~dat()

{

printf("Дата ==> %2d-%2d-%4d\n",day,month,year);

}

//-----------------------------------------------

dat a("12-12-1990");

// Внешняя переменная - конструктор вызывается перед main()

dat b[10]; //Массив объектов - конструктор без параметров вызывается перед main()

void xxx(dat &p) {

dat c(12,12); //Вызывается Конструктор dat(int,int,int) для автоматического объекта

dat d = p; // Конструктор для автоматического объекта не

... // вызывается, т.к. объект инициализируется копированием

} // При выходе из функции вызываются деструкторы для объектов c и d

void main(){

int i,n;

scanf("%d",&n);

dat *p = new dat[n]; // Создание массива динамических объектов

// конструктор без параметров явно вызывается

for (i=0; i<10; i++) // n раз

xxx(b[i]); // При вызове неявно передается ссылка на b[i]

for (i=0; i< n; i++) //

xxx(p[i]) // При вызове неявно передается ссылка на p[i]

delete[n] p; // Уничтожение массива динамических объектов

// деструктор явно вызывается n раз

} // Деструктры для a и b[10] вызываются после выхода из main()

}

Порядок вызова конструкторов

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

· Инициализация осуществляется в порядке, определяемом порядком объявления

· Члены инициализируются в порядке объявления

· Void-конструкторы базовых классов, которые явно не указаны в нициализирующем списки, вызываются после конструкторов явно инициализируемых базовых классов, в том порядке, в котором они следуют в объявлении класса. Эти void-конструкторы вызываются перед любыми конструкторами полей данных.

class Parent1 {

int p1;

public:

Parent1(void) { cout << “ Construct Parent1 free parameter”; p1=0; }

Parent1(int ap):p1(ap) { cout << “Construct Parent1 1 parameter”; }

~Parent1(void) { cout << “ destruct Parent1”; }

};

class Parent2 {

int p2;

public:

Parent2(void) { cout << “ Construct Parent2 free parameter”; p2=0; }

Parent2(int ap):p2(ap) { cout << “Construct Parent2 1 parameter”; }

~Parent2(void) { cout << “ destruct Parent2”; }

};

class Parent3 {

int p3;

public:

Parent3(void) { cout << “ Construct Parent3 free parameter”; p3=0; }

Parent3(int ap):p3(ap) { cout << “Construct Parent3 1 parameter”; }

~Parent3(void) { cout << “ destruct Parent3”; }

};

class Member {

int m;

public:

Member(void) { cout << “ Construct Member free parameter”; m=0; }

Member(int ap):m(ap) { cout << “Construct Member 1 parameter”; }

~Member(void) { cout << “ destruct Member”; }

};

class Child: public Parent3,public Parent2,public Parent3 {

Member mem1,mem2 ;

public:

Child(void) { cout << “ construct child free parameter”; }

Child(Member achildMember): childMember(achildMember) {

cout << “ Construct Child with 1 parameter”; }

Child(int v1,int v2,int v3,int m1,int m2): mem2(m1),

Parent1(v1),Parent3(v3),Parent2 (v2),mem1(m2){

cout <<“ construct child with 3 parameter”; }

~Child(void) { cout << “destruct Child”; }

};

main() {

Member m(16); // Construct Member with 1 parameter

// destruct Member

Child child1; // Construct Parent3 free parameter

// Construct Parent2 free parameter

// Construct Parent1 free parameter

// Construct Member free parameter

// Construct Member free parameter

// construct child free parameter

// destruct Child

// destruct Member

// destruct Member

//destruct Parent1

// destruct Parent2

// destruct Parent3

Child child2(m); // Construct Parent3 free parameter

// Construct Parent2 free parameter

// Construct Parent1 free parameter

// Construct Member free parameter

// Construct Child with 1 parameter

// destruct Child

// destruct Member

// destruct Member

// destruct Parent1

// destruct Parent2

// destruct Parent3

Child child3(10,20,30,40,50); // Construct Parent3 with 1 parameter

// Construct Parent2 free parameter

// Construct Parent1 with 1 parameter

// Construct Member 1 parameter 50

// Construct Member 1 parameter 40

// construct child with 3 parameter

// destruct Child

// destruct Member

// destruct Member

// destruct Parent1

// destruct Parent2

// destruct Parent3

}