Конструкторы и деструкторы
Конструктор – это функция-член класса, которая вызывается автоматически для создания и инициализации экземпляра класса. Известно, что экземпляры структур можно инициализировать при объявлении:
struct Student{
int semhours;//public по умолчанию
char subj; }
Student s={0,”c” };//объявление и инициализация
При использовании класса приложение не имеет доступа к его защищенным элементам, поэтому для класса подобную инициализацию выполнить нельзя. Для этой цели используются специальная функция-член класса, инициализирующая экземпляр класса и указанные переменные при создании объекта. Подобная функция вызывается всегда при создании объекта класса. Это и есть конструктор – функция-член класса, которая имеет то же имя, что и класс. Конструктор может быть подставляемой (inline) и неподставляемой функциями. Рассмотрим пример:
#include<iostream.h> // пример 22
class Student{
int semhours;
char subj;
public:
Student() //inline-конструктор 1 без параметров
{semhours=0; subj='A';}
Student(int, char); //объявление конструктора 2 с параметрами
};
Student::Student(int hours,char g)//конструктор 2
{semhours=hours; subj=g;}
int main(){
student s(100,'A'); //конструктор 2
student s1[5]; //конструктор1 вызывается 5 раз
return 0;
}
Там, где находятся объявления s и s1, компилятор помещает вызов соответствующего конструктора Student().
Конструктор не имеет типа возвращаемого значения, хотя может иметь аргументы и быть перегружаемым. Он вызывается автоматически при создании объекта, выполнении оператора new или копировании объекта. Если конструктор отсутствует в классе, компилятор С++ генерирует конструктор по умолчанию.
Деструктор вызывается автоматически при уничтожении объекта, и имеет то же имя, что и класс, но перед ним стоит символ “~”. Деструктор можно вызывать явно в отличие от конструктора. Конструкторы и деструкторы не наследуются, хотя производный класс может вызывать конструктор базового класса.
В следующем примере используется конструктор копирования, который необходимо вводить из-за проблемы, связанной с динамической памятью. Когда объект передается в функцию по значению, то создается его копия, при этом конструктор не вызывается. При выходе из функции объект уничтожается и вызывается деструктор, освобождающий его динамическую память. В этом случае исходный объект, использующий ту же динамическую память, что и копия, может быть поврежден. Аналогично, если функция возвращает объект, содержащий динамические переменные, при выходе из функции копия этого объекта разрушается вызовом деструктора. Возвращаемый объект при этом также может быть разрушен. Равным образом это повторяется и при инициализации объявляемого объекта вида:
Class_type B=A;
При этом происходит побитовое копирование объекта А в объект В. Однако для массива в объекте B память динамически не выделяется, а происходит только копирование указателя на массив. Если затем объект А, содержащий динамический массив, разрушается вызовом деструктора, то объект Втакже разрушается. Поэтому при таком объявлении должен вызваться конструктор копирования.
Общая форма конструктора копирования имеет вид:
имя_класса (const имя_класса &ob) {
/*выделение памяти и копирование в нее ob*/
}
Здесь ob является ссылкой на объект в правой части инициализации.
Рассмотрим пример использования конструктора копирования при создании безопасного динамического массива.
/*создается класс "безопасный" массив. Когда один объект-массив используется для инициализации другого, для выделения памяти вызывается конструктор копирования*/
#include <iostream.h> // пример 23
#include <stdlib.h>
#include <conio.h>
class Array {
int *p;
int size;
public:
Array (int s) { cout << "constructor1 \n";
size=s;
p=new int[size];
if(!p) exit(1);
}
~Array() {delete [ ] p;}
Array(const Array &a);//конструктор копирования
void put(int i, int j) {
if(i>=0 && i<size) p[i]=j;
}
int get(int i) {
return p[i]; }
};
/* конструктор копирования. Память выделяется для копии, адрес памяти передается p.*/
Array:: Array(const Array &a) {
p=new int[a.size]; //выделение памяти для копии
if(!p) exit(1);
for(int i=0;i<a.size;i++)
p[i]=a.p[i];//копирование содержимого в память для копии
cout << "constructorcopy2\n";}
int main(){
Array y(5); //вызов обычного конструктора
for(int i=0;i<5;i++){
y.put(i, i+5); //помещение в массив нескольких значений
cout << y.get(i); //вывод на экран num
}
cout << "\n";
Array x=y; /*создание массива x и инициализация, вызов конструктора копирования */
// другой способ вызова - объявление: Array x(y);
y=x;//конструктор не вызывается !!!
while(!kbhit());
return 0;
}
Результат:
constructor1
constructorcpy2
Объект y используется при инициализации объекта х с помощью конструктора копирования. При этом выделяется динамическая память, адрес которой в x.p, и производится копирование y в x. После этого y и x не разделяют одну и ту же динамическую память. Если же присваивание осуществляется не при инициализации, то конструктор копирования не вызывается, а происходит побитовое копирование объекта. В результате двумя объектами используется одна и та же динамическая память. Так происходит в случае присваивания y=x в примере.
В следующем примере конструктор копирования вызывается при передаче объекта-строки в качестве аргумента функции show():
#include <iostream.h> // пример 24
#include <stdlib.h>
#include <conio.h>
class Strtype {
char *p;
int l;
public:
Strtype(char *s='\0') //конструктор
{ l=strlen(s);
p=new char[l+1];
if(!p) {cout << "ошибка при выделении памяти\n";exit(1); }
strcpy(p, s);
cout<<"constr1\n";
}
Strtype(const Strtype &o)//конструктор копирования
{ l=strlen(o.p);
p=new char[l+1];//выделение памяти для новой копии
if(!p) {cout << "ошибка памяти\n";exit(1);}
strcpy(p, o.p); //передача строки в копию
cout<<"constrcpy\n";
}
~Strtype() {delete [ ] p;cout<<"destr\n";} //деструктор
char *get() {return p;}
};
void show(Strtype x)
{ char *s;
s=x.get();cout << s << "\n";}
int main(){
Strtype a("Hello"), b("There");//constr1,constr1
show(a);//конструктор копирования constrcpy,Hello,destr
show(b);//конструктор копирования constrcpy,There,destr
while(!kbhit());
return 0;
}
Когда функция show() завершается и х выходит из области видимости, то освобождается память х.р, однако не та, которая используется передаваемым в функцию объектом.