Создание и уничтожение объектов
Любому объекту требуется память и некоторое начальное значение. В C++ это обеспечивается с помощью объявлений, являющихся одновременно определениями. В большинстве случаев при обсуждении объявлений мы подразумеваем именно такие объявления. Например, в
void foo ()
{
int n = 5 ;
double z[10] = {0. 0};
struct gizmo { int i, j; } w= {3, 4};
…..
}
все объекты создаются при входе в блок, когда вызывается foo().
При создании сложных агрегатов пользователь вправе ожидать аналогичного подхода к управлению объектами, определенным классом. Чтобы клиент мог использовать объекты как собственные типы, класс нуждается в механизме создания и уничтожения объектов.
Конструктор (constructor) — это функция-член, имя которой совпадает с именем класса. Он создает значения типа своего класса. Этот процесс включает в себя инициализацию членов данных и (зачастую) распределение свободной памяти с помощью new. Деструктор (destructor) — это функция-член, имя которой представляет собой имя класса, предваряемое символом - (тильда). Обычно деструктор предназначен для уничтожения значений типа класса, как правило, с помощью delete.
Из этих двух специальных функций-членов более сложны конструкторы. Они могут быть перегружены, могут принимать аргументы; ни то, ни другое невозможно для деструкторов1. Конструктор вызывается: когда связанный с ним тип используется в определении; когда для передачи значения функции применяется вызов по значению; или когда результат, возвращаемый функцией, предполагает создание значения соответствующего типа. Деструкторы вызываются неявно, когда объект выходит за пределы области видимости. Конструкторы и деструкторы не имеют возвращаемого типа и не могут использовать инструкцию return выражение.
6.1. Классы с конструкторами
Простейшее применение конструктора — использование его при инициализации.
Первый пример — реализация типа данных mod_int для хранения чисел, над которыми производятся вычисления по модулю.
//Числа для вычислений по модулю и инициализация конструктора
class mod_int {
public:
mod_int(int i); //объявление конcтруктора
void assign(int i) { v = i % modulus; }
void print() const { cout <<v << '\t'; }
const static int modulus =60;
private:
int v;
};
//определение конструктора
mod_int::mod_int(int i) { v = i % modulus; }
const int mod_int::modulus;
Целая переменная v ограничена значениями 0,1,2,..modulus-1. Ответственность за выполнение этого ограничения лежит на программисте; все функции-члены должны вести себя, соблюдая это правило.
Функция-член mod_int:: mod_int (int) является конструктором. Она не имеет возвращаемого типа. Она вызывается, когда объявляются объекты типа mod_int. Это функция одного аргумента. При вызове конструктор ожидает выражение, преобразуемое по умолчанию к его целому параметру. Затем он создает и инициализирует объявленную переменную.
Вот некоторые примеры объявлений с использованием данного типа:
mod_int a(0); //a.v = 0
mod_int b(61); //a.v = 1
1К чести деструкторов надо сказать, что они (в отличие от конструкторов) могут быть виртуальными. — Примеч. перев.
но не так:
mod_int а; //недопустимо, т.к. нет списка параметров
Поскольку у этого класса лишь один конструктор со списком аргументов int, объявление mod_int должно получать целое выражение, передаваемое в качестве инициализующего значения. Заметьте, что не допуская объявления переменной типа mod_int без выражения-инициализатора, мы тем самый предотвращаем ошибки на этапе выполнения из-за неинициализованных переменных.
Конструктор по умолчанию
Конструктор, не требующий аргументов, называется конструктором по умолчанию. Это может быть конструктор с пустым списком аргументов, или конструктор, у которого все аргументы имеют значения по умолчанию. Конструктор по умолчанию имеет специальное назначение при инициализации массивов объектов своего класса.
Зачастую удобно перегружать конструктор с помощью нескольких объявлений функций. В нашем примере было бы желательно, чтобы v имела значение по умолчанию 0. Если мы добавим конструктор по умолчанию
mod_int () { v = 0; }
как функцию-член mod_int, то можно будет сделать следующие объявления:
mod_int; sl, s2; //оба инициализуют закрытый член v нулем mod_int d[5]; //массивы правильно инициализованы
В обоих объявлениях будет вызван конструктор с пустым списком параметров.
Если у класса нет конструктора, система предоставляет конструктор по умолчанию. Если конструктор у класса есть, но нет конструктора по умолчанию, размещение массива вызовет синтаксическую ошибку.
Обратите внимание, что в нашем примере с классом mod_int один и тот же конструктор может служить и для обычной инициализации, и в качестве конструктора по умолчанию:
inline: mod_int: :mod_int (int i = 0)
(v = i % modulus; }
Инициализатор конструктора
Существует специальный синтаксис для инициализации отдельных частей объекта с помощью конструктора. Инициализаторы конструктора (constructor initializer) для членов структуры и класса могут быть заданы через запятую в списке, который идет после двоеточия за списком параметров конструктора и предшествует телу. Давайте перепишем предыдущий пример:
//Конструктор по умолчанию для mod_int
mod_int: :mod_int(int i = 0) : v(i % modulus) {}
Обратите внимание, как инициализация заменила присваивание. Отдельные члены должны быть инициализованы как:
имя_члена (список выражений)
Не всегда членам можно присвоить значения в теле конструктора. Список инициализаторов необходим, когда нестатический член либо постоянен, либо является ссылкой.