Малышев Сергей Михайлович
Речь пойдет о четырех очень специальных членах, которые должны учитываться при создании каждого класса. Это конструктор по умолчанию, конструктор копий, операция присваивания и деструктор. Заметьте, это вовсе не значит, что вы должны обязательно каждый раз писать все эти четыре функции для каждого класса, который вы создаете. Речь идет только о том, что вы должны принимать их во внимание при создании любого класса и делать выводы об их необходимости.
Эти четыре члена в принципе являются функциями, но не типичными. Они выглядят совсем как функции, но, как несложно заметить, некоторые из них не возвращают никаких значений. Эти функции предназначены для создания (инициализации), копирования и удаления (разрушения) объектов класса.
BR> Очень подробно мы рассмотрим проблемы копирования и присваивания для классов, посвятив этому несколько отдельных материалов. А пока, все, что излагается ниже, можно рассматривать, как вводную часть к этим довольно сложным вопросам.
1. Конструкторы
Конструкторы - это члены классов, используемые для создания объектов-экземпляров классов. Есть несколько разновидностей конструкторов, в их числе есть довольно своеобразные. Но основное их назначение в любом случае одно и то же: обеспечение удобного способа создания объекта-экземпляра класса.
Мы рассмотрим конструктор по умолчанию, конструктор копий, аргументы по умолчанию в конструкторе и другие конструкторы.
Как уже было отмечено, конструкторы - это функции-члены, не возвращающие никаких значений (даже типа void). Другой их особенностью является то, что их имя должно в точности, включая регистр символов, совпадать с именем класса. То есть, если класс называется Any_Class, то его конструктор также должен называться Any_Class.
2. Конструктор по умолчанию.
Итак, конструктор по умолчанию (default constructor) - это конструктор, не принимающий (не имеющий) аргументов.
Таким образом, конструктор по умолчанию для некоего произвольного класса будет выглядеть так:
class ANY_CLASS
{
public:
ANY_CLASS(); //конструктор по умолчанию
... //тут все остальное
};
Обычно конструкторы объявляются в открытой (public) секции класса, поскольку деятельность конструкторов заключается в создании объекта типа класса, и они вызываются извне класса. Вызовы конструкторов, как правило, происходят неявно. Например, создание одиночного объекта типа ANY_CLASS может выглядеть следующим бразом:
ANY_CLASS ас; // ас - это объект класса ANY_CLASS
Заметьте, что в этом операторе совершенно отсутствуют скобки, конструирование - это неявная операция.
Массив объектов типа ANY_CLASS может быть создан так:
ANY_CLASS aac[10]; // aас - это массив из 10 элементов
Как видите, синтаксис объявления массива объектов точно такой же, как и синтаксис объявления статического массива данных любого базового (встроенного) типа. Одна из задач языка C++ состоит в предоставлении пользователям возможности обращаться со сложными типами данных таким же образом, как и со встроенными.
Благодаря неявной природе конструирования объектов достигается первый ее аспект: создание объекта выглядит точно так же, как и создание обычной переменной.
Кстати, создать МАССИВ объектов можно ТОЛЬКО в том случае, если для класса определен конструктор по умолчанию.
3. Конструктор копий.
Работа конструктора копий (copy constructor) заключается в предоставлении возможности инициализации (создания) нового объекта из уже существующего. Для объяснения глубинных механизмов этого процесса потребовалась бы не одна глава, поэтому мы ознакомимся с ними пока вкратце.
Общий синтаксис конструктора копий таков:
My_Class(const My_Class&); // здесь My_Class - это имя класса
В конструкторе копирования класса My_Class в качестве параметра используется ссылка на объект этого класса. Причём эта ссылка объявляется со спецификатором const. И в этом нет ничего странного.
Как известно, выражение вызова функции с параметром типа X ничем не отличается от выражения вызова функции, у которой параметром является ссылка на объект типа X. При вызове такой функции не приходится копировать объекты как параметры. Передача адреса не требует копирования объекта, а значит, при этом не будет и рекурсии.
Внутрь тела конструктора копий обычно помещается серия присваиваний, посредством которых каждому элементу объекта-аргумента присваивается значение соответствующего элемента вызывающего объекта.
Здесь есть много моментов, которые следует иметь в виду, и все они исключительно важны для понимания классов C++, но дальнейшее обсуждение конструктора копий мы отложим до главы, специально посвященной копированию и присваиванию.
4. Аргументы по умолчанию в конструкторе.
У любых функций могут быть аргументы по умолчанию. Это относится к конструкторам в той же степени, как и к любым остальным функциям-членам и глобальным функциям. Аргумент по умолчанию - это значение, которое присваивается аргументу, если пользователь явно не задал иное значение.
Поскольку сейчас речь идет конкретно о конструкторах, рассмотрим соответствующий пример. Аргументы по умолчанию удобны в тех случаях, когда известно определенное (или предпочтительное) значение аргумента, но при этом желательно сохранить возможность задания другого значения при создании объекта. Рассмотрим в качестве примера некий гипотетический класс, описывающий файл.
class FILE
{
public:
FILE(char *FileName = "file.bin");
// "file.bin" - это аргумент по
// умолчанию ... //все остальное
};
Если аргументу FileName типа char * в конструкторе не передать какое либо значение, то будет автоматически подставлено значение "file.bin".
Таким образом, экземпляры класса FILE можно создавать следующими способами:
FILE IniFile; //будет создан файл с именем file.bin
FILE Archive("Archive.dat"); //будет создан файл с именем Archive.dat
Правило использования аргументов по умолчанию таково: аргументу можно задать значение по умолчанию, если он находится правее всех в списке аргументов, или если все аргументы правее него имеют значения по умолчанию.
Ниже приведены несколько фиктивных конструкторов, демонстрирующих примеры правильного и неправильного употребления аргументов по умолчанию:
DATA(int а=0, int b); // явная ошибка: DATA F( , 5) смотрится глупо...
DATA(int а, int b=10); // правильно, можно создать объекты DATA G(5);
// или DATA G(3, 4);
DATA(int a=0, int b=10);// правильно, можно создать объекты DATA Н(3, 4);
// или DATA R;
Правило для аргументов по умолчанию было введено для того, чтобы не возникало ситуаций типа "пробел запятая аргумент" (см. первый пример для объекта F( , 5)), которые весьма чреваты ошибками, да и выглядят неважно.
Необходимо также отметить следующее: конструктор, все аргументы которого снабжены значениями по умолчанию, может вызываться и с аргументами, и без аргументов, то есть при вызове выглядеть как обычный конструктор по умолчанию (см. пример для DATA Н(3, 4); и DATA R;).
Поэтому желательно избегать неопределенности, возникающей при одновременном задании в классе конструктора по умолчанию, то есть без аргументов, и конструктора, у которого все аргументы имеют значения по умолчанию.
5. Конструкторы в целом.
Итак, небольшое заключение. Конструкторы предназначены для того, чтобы обеспечить разработчика класса средством для удобной инициализации каждой из составных частей класса. Количеству и разнообразию аргументов конструктора нет предела. Однако, обычно типы аргументов должны соответствовать данным-членам класса, то есть тому, что в конце концов надо инициализировать.
Требований к созданию конструкторов немного: вы вынуждены учитывать, что конструктор вызывается неявно, что он не имеет возвращаемого значения и что его имя в точности должно совпадать с именем его класса.
6. Деструкторы
Деструкторы выполняют работу, обратную той, что проделывают конструкторы. Хотя класс может иметь несколько конструкторов, но деструктор может быть только один. Синтаксис деструктора очень похож на синтаксис конструктора по умолчанию. Точно также деструктор не имеет аргументов, все различие заключается в том, что деструктор, будучи по своей сути функцией, парной конструктору, имеет то же имя, что и класс, но с приставкой в виде операции дополнения (~).
То есть деструктор любого класса имеет такую форму:
class ANY_CLASS
{
public:
ANY_CLASS(); //конструктор по умолчанию
ANY_CLASS(int d); //еще один конструктор
~ANY_CLASS(); //а это - деструктор
};
Деструктор почти всегда вызывается неявно. Вызов деструктора происходит либо при выходе объекта за пределы своей области видимости, либо при уничтожении динамического созданного (операцией new) объекта операцией delete.
7. Виртуальный деструктор.
Если класс может иметь наследников, то предпочтительнее использовать виртуальный деструктор. Синтаксис виртуального деструктора точно такой же, как и у любого другого деструктора, за исключением того, что его объявление начинается с ключевого слова virtual.
class ANY_CLASS
{
public:
ANY_CLASS(); //конструктор по умолчанию
vrtual ~ANY_CLASS(); //а это - виртуальнный деструктор
};
Объявление деструктора виртуальным не отразится на производительности. Так что имеет смысл всегда делать его виртуальным, если нет очень веских причин воздержаться от этого.
Отказ от применения виртуальных деструкторов может привести к утечкам памяти в производных классах, но это тема для отдельного разговора. К ней мы вернемся более подробно позднее.
Приведенный ниже пример придуман исклю
include "iostream.h"
// Этот класс просто демонстрирует неявные вызовы
// конструктора и деструктора
class DEMO
{
public:
DEMO() { cout " "constructor" " endl; }
virtual ~DEMO() { cout " "destructor" " endl; }
};
void main(void)
{
DEMO staticDemo; // статическое размещение, деструктор вызывается при
// выходе за пределы области видимости
DEMO *dynamicDemo = new DEMO;
// динамическое размещение,
// деструктор вызывается при уничтожении объекта