yandex rtb 1
ГоловнаЗворотній зв'язок
yande share

Объектно-ориентированное программирование

Лекция 6.

Конструктор по умолчанию. Копирующий конструктор. Деструктор.

 

Как упоминалось в предыдущей лекции, С++ позволяет определить несколько конструкторов. Среди них особое место занимают два – конструктор по умолчанию и копирующий конструктор. Важность их связана с тем, что именно эти два конструктора предоставляет компилятор в отсутствии определения конструкторов класса.

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

Ранее демонстрировались примеры классов, в которых конструктор явно не определен. Однако это не означает, что для данного класса конструктор вообще отсутствует. Компилятор предоставляет в таком случае свой конструктор по умолчанию, основные действия которого сводятся к выделению памяти под члены класса. Но если требуется произвести какие-либо дополнительные действия (выделение динамической памяти, увеличение счетчика экземпляров класса и т.п.), необходимо явно определить конструктор по умолчанию.

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

Для класса c_pair из предыдущей лекции конструктор по умолчанию может быть определен следующим образом:

s_pair:s_pair() : first(0) {

   second=new char;

   *second='\0';

   count++;

}

Данный конструктор не имеет параметров, а переменные first и second инициализирует 0 и пустой строкой соответственно. При этом увеличивается счетчик числа экземпляров класса.

 

Вторым важным видом конструкторов является копирующий конструктор. Как и конструктор по умолчанию, копирующий конструктор предоставляется компилятором. Однако в отличие от конструктора по умолчанию, даже если в классе имеется явно определенный конструктор, но нет определения копирующего конструктора, компилятор предоставляет свой копирующий конструктор (это утверждение справедливо не для всех компиляторов).

Сигнатура копирующего конструктора такова:

имя_класса::имя_класса(const имя_класса& source);

Когда же используется копирующий конструктор? Самое простое, явное и не главное его применение – инициализация одного экземпляра класса с помощью другого. Наиболее важным является его использование, когда экземпляр класса передается по значению в качестве аргумента функции или возвращается в качестве значения функции.

Если в программе определена функция, имеющая в качестве аргумента экземпляр класса, компилятор использует копирующий конструктор для создания локальной копии экземпляра класса в функции. Если копирующий конструктор не определен, компилятор использует свой копирующий конструктор, который инициализирует члены класса один за другим. Проблема может возникнуть, если такой класс в качестве члена имеет указатель. В таком случае копирующий конструктор, предоставляемый компилятором, не будет выделять память под динамические переменные, а только перепишет ссылки. Таким образом, в локальной копии ссылки будут указывать на ту же область памяти, что  и в источнике, что может нарушить нормальную работу программы. Поэтому при определении класса желательно определять явно копирующий конструктор.

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

 

#include <iostream.h>

#include <cstring>

class s_pair {

private:

   int first;

   char* second;

   static int count;

public:

   s_pair(int first, char* string);

   s_pair();

   s_pair(const s_pair& source);

   ~s_pair();

   void setfirst(int first) {s_pair::first=first;}

   int getfirst() { return first;}

   void setsecond(char* string) {

               int len=strlen(string);

               delete [] second;

               second=new char[len+1];

               strcpy(second,string);

   }

   void getsecond(char* string) {

               strcpy(string,second);

   }

   static int getcount() {return count;}

};

int s_pair::count=0;

s_pair::s_pair(int first,char* string) : first(first){

   int len=strlen(string);

   second=new char[len+1];

   strcpy(second,string);

   count++;

}

s_pair::s_pair() : first(0) {

   second=new char;

   *second='\0';

   count++;

}

s_pair::s_pair(const s_pair& source): first(source.first) {

   int len=strlen(source.second);

   second=new char[len+1];

   strcpy(second,source.second);

   count++;

}

s_pair::~s_pair() {

   count--;

   delete [] second;

   second=0;

}

void print(s_pair element) {

   char str[20];

   element.getsecond(str);

   cout <<element.getfirst()<<" "<<str<<endl;

}

void main() {

   s_pair a(0,"it is string"), *b;

   cout <<"count="<<s_pair::getcount()<<endl;

   print(a);

   s_pair c[2];

   cout <<"count="<<s_pair::getcount()<<endl;

   print(c[0]);

   print(c[1]);

   b=new s_pair(1,"it is outher string");

   cout <<"count="<<s_pair::getcount()<<endl;

   b->setsecond("replace");

   print(*b);

   delete b;

   b=0;

   cout <<"count="<<s_pair::getcount()<<endl;

}

 

Вывод этой программы будет следующим:

 

count=1

0 it is string

count=3

0

0

count=4

1 replace

count=3

 

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

void main() {

   char str[20];

   s_pair a(12,"it is string"), *b;

   cout <<"count="<<s_pair::getcount()<<endl;

   print(a);

   a.getsecond(str);

   cout<<a.getfirst()<<" "<<str<<endl;

   s_pair c[2];

   cout <<"count="<<s_pair::getcount()<<endl;

   print(c[0]);

   print(c[1]);

   b=new s_pair(1,"it is outher string");

   cout <<"count="<<s_pair::getcount()<<endl;

   b->setsecond("replace");

   print(*b);

   delete b;

   b=0;

   cout <<"count="<<s_pair::getcount()<<endl;

}

Вывод: этой программы таков:

count=1

12 it is string

12 ________________________0[1]

count=2

0

0

count=1

1 replace

После этого в программе возникает ошибка и программа аварийно заканчивает работу. Как мы видим, в программе неверно подсчитывается количество экземпляров класса и перестают работать указатели на строки. Связано это с тем, что по окончании работы функции print локальные копии уничтожаются. Производит это так называемый деструктор. А при отсутствии копирующего конструктора, как уже говорилось ранее, создаются копии указателей. При вызове деструктора уничтожаются не только локальные копии, но и информация, на которую указывают данные указатели.

 

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

Вызов деструктора происходит явно, когда применяется оператор delete и неявно, когда программа выходит за область видимости объекта (при выходе из блока или функции, в которой объявлен объект).

   В описанной выше программе деструктор выполняет следующие действия:

-          освобождает динамически выделенную память

-          уменьшает счетчик числа экземпляров класса на 1

 

8