Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
пиппец 2.doc
Скачиваний:
106
Добавлен:
07.06.2015
Размер:
3.16 Mб
Скачать

69. Механизмы создания и уничтожения объектов

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

В C++ предусмотрены специальные функции-члены класса, которые в большинстве случаев вызываются не программистом, а компилятором языка. Функции, выполняющие инициализацию объектов абстрактных типов, называются конструкторами, а функции, управляющие уничтожением объектов - деструкторами.

Конструктор - это просто специальная функция-член класса, отличительной чертой которой является то, что ее имя всегда совпадает с именем класса. Например, для класса String конструктор может быть определен как встраиваемая функция:

class String {

char * str; int row, col; public:

/ /Конструктор : String (char* s) { str = new char[strlen(s)+1];

strcpy(str,s);

}

};

либо как внешняя функция:

class String {

char * str;

int row, col; public:

//Объявление конструктора:

String (char* s);

};

//Внешнее определение конструктора:

{String ::String (char * s)

str = new char [strlen(s } +1] ;

strcpy (str,s) ;}

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

• динамически выделяется память для строки;

• передаваемая в качестве аргумента строка копируется по адресу str (становится значением данного-члена класса);

Невозможно создать объект абстрактного типа без вызова конструктора. Таким образом, такой объект всегда будет инициализирован. Для конструктора не указывается тип результата(даже void). Конструктор вызывается неявно каждый раз при объявлении объекта класса или создании его операцией new. Например, объявление

String s1 ("Строка") ;

создает объект s 1 и одновременно инициализирует его.

Конструктор без аргументов, жестко задаются все данные объекта - строка и позиция ее вывода на экран: */

String :: String () {

str = new char [sizeof(ХХХХХХХХХ")];

strcpy (str, "XXXXXXXXX");

row =10;

col = 35;

}

/*

Конструктор инициализирует саму строку значением, переданным в качестве аргумента; позиция вывода строки на экран задается жестко в конструкторе: */

String :: String (char* s) {

str = new char [strlen(s)+1];

strcpy (str, s);

row = 10; • ,*

col = 35; }

/*

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

void f ()

{

String s1;

String s2("Вторая строка");

String s3{''Третья строка", 10, 15);

}

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

class String {

char * str;

int row, col; public:

//Объявление конструктора:

String (char * = "XXXXXXXX”, int = 5, int = 5);

};

//Определение конструктора:

String ::String (char * s, int x, int y)

{

str = new char[strlen(s)+1];

strcpy(str,s);

row = x; col - y; }

Отличие конструктора от остальных функций-членов класса заключается в следующем:

• конструктор имеет строго определенное имя, совпадающее с именем класса;

• конструктор не имеет явно указанного возвращаемого значения;

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

Деструкторы

Важную роль наряду с инициализацией объектов классов имеет и обратная к ней операция - удаление таких объектов. В частности, конструкторы многих классов выделяют память под объекты динамически, и после того, как необходимость в таком объекте отпадает, его рекомендуется удалить. Это удобно выполнить в деструкторе - функции, которая вызывается для объекта, когда он выходит из области существования. Имя деструктора, как и конструктора, не может быть произвольным, оно образуется символом ~ (тильда) и именем класса ("дополнение конструктора"). Например:

class String {

//... public:

//...

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

~String () {delete [] str; }

//...

};

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

оформлен в виде внешней функции. В отличие от конструктора, деструктор не может иметь аргументов.

Вызов деструктора для объекта абстрактного типа производится автоматически при его выходе из области существования. Для локальных переменных деструктор вызывается при выходе из блока, в котором эта переменная была объявлена. Для глобальных переменных вызов деструктора является частью процедуры завершения программы, выполняемой после функции main (). Если объект абстрактного типа создан динамически (с помощью операции new), то деструктор для него вызывается из операции delete.

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

деструктор:

• имеет строго определенное имя;

• не имеет никакого возвращаемого значения, в его объявлении не указывается никакой тип результата, даже void;

• не может иметь аргументов (следовательно, деструктор не может быть перегружен);

• вызывается автоматически в двух случаях: когда объект, созданный объявлением, выходит из области своего существования; либо когда объект, созданный операцией new, удаляется операцией delete. В отличие от конструктора, деструктор может быть вызван явно, но с обязательным указанием его полного имени.

Конструктор и операция new

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

class String {

char * str;

int row, col; public:

//Конструктор:

String ();

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

~StringO {delete str;}

};

void main() {

//Создание объекта типа String:

String* ptr = new String;

// . . .

}

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

class String

{

char * str;

int row, col; public:

//Конструктор:

String (char*);

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

~String() {delete str;} };

void main() {

//Создание объекта типа String:

String* ptr = new String("Строка") ;

}

В случае с классами под именем типа понимается имя конструктора (которое совпадает с именем типа), а под инициализирующим выражением - список фактических аргументов конструктора, то есть операция new в качестве своего операнда использует обращение к конструктору. Такая аналогия позволяет без труда ориентироваться в синтаксисе операции выделения памяти для объектов классов.

Если в операции new происходит обращение к конструктору без аргументов (неважно, является ли он конструктором по умолчанию или нет), то допустимы обе следующие формы записи:

String* ptrl = new String(); String* ptr2 = new String;

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

String* р = new String;

компилятор выдаст сообщение об ошибке (не найден конструктор String: ::String ()). В этом случае требуется явно определить конструктор без аргументов.

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

Конструктор копирования – это конструктор, первым (и единственным) аргументом которого является ссылка на объект того класса, в котором этот конструктор объявлен.

То есть, в классе С объявление конструктора копирования имеет вид:

С (const C&);

Если конструктор копирования явно не определён, то компилятор генерирует его. И он будет вызван автоматически всякий раз, когда требуется копия объекта в следующих ситуациях:

  • при инициализации одного объекта другим;

  • при передаче объекта по значению в функцию;

  • при возврате объекта из функции по значению.

(В последних двух случаях, как мы знаем, создаётся временная копия объекта в стеке.)

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

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