До сих пор переменные-члены объектов задавались прямо в теле конструктора. Выполнение конструктора происходит в два этапа: инициализация и выполнение тела конструктора.
Большинство переменных может быть задано на любом из этих этапов: как во время инициализации, так и во время выполнения конструктора. Но логически правильнее, а зачастую и эффективнее, инициализировать переменные-члены во время инициализации конструктора. В следующем примере показана инициализация переменных-членов:
CAT: // имя конструктора и список параметров
itsAge(5), // инициализация списка
itsWeigth(8)
{} // тело конструктора
После скобки закрытия списка параметров конструктора ставится двоеточие. Затем перечисляются имена переменных-членов. Пара круглых скобок со значением за именем переменной используется для инициализации этой переменной. Если инициализируется сразу несколько переменных, то они должны быть отделены запятыми. В листинге 10.4 показана инициализация переменных конструкторов, взятых из листинга 10.3. В данном примере инициализация переменных используется вместо присвоения им значений в теле конструктора.
Листинг 10.4. Фрагмент программного кода с инициализацией переменных-членов
1: Rectangle::Rectangle:
2: itsWidth(5),
3: itsLength(10)
4: {
5: }
6:
7: Rectangle::Rectangle (int width, int length):
8: itsWidth(width),
9: itsLength(length)
10: {
11: }
Результат:
ОтсутствуетАнализ:
Некоторые переменные можно только инициализировать и нельзя присваивать им значения: например, в случае использования ссылок и констант. Безусловно, переменной-члену можно присвоить значение прямо в теле конструктора, но для упрощения программы лучше по возможности устанавливать значения переменных-членов на этапе инициализации конструктора.Конструктор-копировщик
Помимо конструктора и деструктора, компилятор по умолчанию предоставляет также конструктор-копировщик, который вызывается всякий раз, когда нужно создать копию объекта.
Когда объект передается как значение либо в функцию, либо из функции в виде возврата, всегда создается его временная копия. Если в программе обрабатывается объект, созданный пользователем, то для выполнения этих операций вызывается конструктор- копировщик класса, как было показано на предыдущем занятии в листинге 9.6.
Все копировщики принимают только один параметр — ссылку на объект в том же классе. Разумно будет сделать эту ссылку константной, так как конструктор не должен изменять передаваемый в него объект. Например:
CAT(const CAT & theCat);
В данном случае конструктор CAT принимает константную ссылку на объект класса CAT. Цель использования конструктора-копировщика состоит в создании копии объекта theCat.
Копировщик, заданный компилятором по умолчанию, просто копирует все переменные-члены из указанного в параметре объекта в переменные-члены нового объекта. Такое копирование называется поверхностным; и, хотя оно подходит для большинства случаев, могут возникнуть серьезные проблемы, если переменные-члены окажутся указателями на ячейки динамической памяти.
Поверхностное копирование создает несколько переменных-членов в разных объектах, которые ссылаются на одни и те же ячейки памяти. Глубинное копирование переписывает значения переменных по новым адресам.
Например, класс CAT содержит переменную-член theCat, которая указывает на ячейку в области динамической памяти, где сохранено некоторое целочисленное значение. Копировщик по умолчанию скопирует переменную theCat из старого класса CAT в переменную theCat в новом классе CAT. При этом оба объекта будут указывать на одну и ту же ячейку памяти (рис. 10.1).
Проблемы могут возникнуть, если программа выйдет за область видимости одного из классов CAT. Как уже отмечалось при изучении указателей, назначение деструктора состоит в том, чтобы очищать память от ненужных объектов. Если деструктор исходного класса CAT очистит свои ячейки памяти, а объекты нового класса CAT все так же будут ссылаться на эти ячейки, то над программной нависнет смертельная опасность. Эта проблема проиллюстрирована на рис. 10.2.
Чтобы предупредить возникновение подобных проблем, нужно вместо копировщика по умолчанию создать и использовать собственный копировщик, который будет осуществлять глубинное копирование с перемещением значений переменных-членов в новые адреса памяти. Этот процесс показан в листинге 10.5
Листинг 10.5. Конструктор-копировщик
1: // Листинг 10.5.
2: // Конструктор-копировщик
3:
4: #include
5:
6: class CAT
7: {
8: public:
9: CAT; // конструктор по умолчанию
10: CAT (const CAT &); // конструктор-копировщик
11: ~CAT; // деструктор
12: int GetAge const { return *itsAge; }
13: int GetWeight const { return *itsWeight; }