Динамическое создание объектов: new и delete

До сих пор мы пробовали создавать только автоматические объекты, то есть локальные переменные внутри OnStart. Объект, описанный в глобальном контексте (вне функции OnStart или какой-либо другой), также был бы автоматически создан (в момент загрузки скрипта) и удален (в момент выгрузки скрипта).

Кроме этих двух режимов мы затронули возможность описать поле объектного типа (в нашем примере это структура Pair, использованная для поля coordinates внутри объекта Shape). Все такие объекты тоже являются автоматическими: они создаются для нас компилятором в конструкторе объекта-"хозяина" и удаляются в его деструкторе.

Однако в программах далеко не всегда удается обойтись только автоматическими объектами. В случае программы рисования нам потребуется создавать фигуры по запросу пользователя. Более того, фигуры нужно будет хранить в массиве, а для этого автоматические объекты должны были бы иметь конструктор по умолчанию (что в нашем случае не так).  

Для подобных ситуаций MQL5 предоставляет возможность динамического создания и удаления объектов. Создание выполняется с помощью оператора new, а удаление — с помощью оператора delete.

 

Оператор new

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

Оператор new возвращает значение особого типа — указатель на объект. Для описания переменной такого типа следует после имени класса добавить символ звездочки '*'. Например:

Rectangle *pr = new Rectangle(1002005075clrBlue);

Здесь переменная pr имеет тип указателя на объект класса Rectangle. Указатели будут более подробно рассмотрены в отдельном разделе.

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

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

Print(pr.toString());

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

 

Оператор delete

Полученные через new указатели следует освобождать по завершении алгоритма с помощью оператора delete. Например:

delete pr;

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

1 undeleted objects left
1 object of type Rectangle left
168 bytes of leaked memory

Терминал сообщает, сколько объектов и какого класса было забыто программистом, а также какой объем памяти они занимали.

После того как для указателя вызван оператор delete, указатель становится недействительным, так как объект уже не существует. Последующая попытка обратиться к его свойствам вызывает ошибку времени исполнения "Обращение по неверному указателю":

Critical error while running script 'shapes (EURUSD,H1)'.
Invalid pointer access.

Работа MQL-программы при этом прерывается.

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

MQL5 имеет встроенную функцию, которая позволяет проверить правильность указателя в переменной — CheckPointer:

ENUM_POINTER_TYPE CheckPointer(object *pointer);

Она принимает один параметр типа указатель на класс и возвращает значение из перечисления ENUM_POINTER_TYPE:

  • POINTER_INVALID — неверный указатель;
  • POINTER_DYNAMIC — действующий указатель на динамический объект;
  • POINTER_AUTOMATIC — действующий указатель на автоматический объект.

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

Следующий макрос упрощает и обеспечивает корректность очистки указателя:

#define FREE(Pif(CheckPointer(P) == POINTER_DYNAMICdelete (P)

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