Обнуление объектов и массивов
Обычно инициализация или заполнение переменных и массивов не вызывает проблем. Так, для простых переменных действительно довольно просто использовать оператор '=' в инструкции определения вместе с инициализацией или присвоить требуемое значение в любой более поздний момент.
Для структур доступна агрегатная инициализация вида (см. раздел Определение структур):
Struct struct = {value1, value2, ...}; |
Но она возможна, только если в структуре нет динамических массивов и строк. Более того, синтаксис агрегатной инициализации нельзя использовать для повторной очистки структуры. Вместо этого необходимо либо присваивать значения каждому полю по отдельности, либо зарезервировать в программе экземпляр пустой структуры и копировать его в очищаемые экземпляры.
Если речь идет еще и о массиве структур, то исходный код быстро разрастется за счет вспомогательных, но необходимых инструкций.
Для массивов, как мы знаем, существуют функции ArrayInitialize и ArrayFill, но они поддерживают только числовые типы: массив строк или структур ими заполнить не получится.
В таких случаях может пригодиться функция ZeroMemory. Она не является панацеей, поскольку имеет существенные ограничения по области применения, но, тем не менее, её стоит взять на вооружение.
void ZeroMemory(void &entity)
Функция может применяться к широкому набору различных сущностей: переменным простых или объектных типов, а также их массивам (фиксированным, динамическим, многомерным).
Переменные получают значение 0 (для чисел) или его эквивалент (NULL для строк и указателей).
В случае массива обнулению подвергаются все его элементы, причем элементы могут быть объектами и в свою очередь содержать объекты. Иными словами, функция ZeroMemory выполняет глубокую очистку памяти за один вызов.
Однако для допустимых объектов есть ограничения. Заполняться нулями могут лишь объекты структур и классов, которые:
- содержат только открытые поля (то есть в них нет данных с типом доступа private или protected);
- не содержат полей с модификатором const;
- не содержат указателей.
Первые два ограничения заложены в компилятор: попытка обнулить объекты с полями, несоответствующими указанным требованиям, вызовет ошибки (см. пример далее).
Третье ограничение относится к разряду рекомендаций: внешнее обнуление указателя затруднит контроль за целостностью данных, что, вероятно, приведет к потере связанного объекта и утечке памяти.
Строго говоря, требование публичности полей в обнуляемых объектах нарушает принцип инкапсуляции, свойственный объектам класса, и потому ZeroMemory преимущественно используется с объектами простых структур и их массивами.
Примеры работы с ZeroMemory приведены в скрипте ZeroMemory.mq5.
Проблемы со списком агрегатной инициализации демонстрируются с помощью структуры Simple:
#define LIMIT 5
|
В функции OnStart или в глобальном контексте мы не можем определить и тут же обнулить объект такой структуры:
void OnStart()
|
Компилятор выдает ошибку "нельзя использовать список инициализации". Она специфична для полей вроде динамических массивов, строковых переменных и указателей. В частности, если бы массив data был фиксированного размера, ошибки бы не возникло.
Поэтому вместо списка инициализации используем ZeroMemory:
void OnStart()
|
Начальное заполнение нулями можно было бы сделать и в конструкторе структуры, но последующие очистки удобнее делать снаружи (или предоставить для этого метод с той же самой ZeroMemory).
Далее в коде определен класс Base.
class Base
|
Поскольку класс далее используется в массивах объектов, обнуляемых с помощью ZeroMemory, мы вынуждены прописать для его полей секцию доступа public (что, в принципе, не типично для классов и сделано для иллюстрации навязываемых требований со стороны ZeroMemory). Также обратите внимание, что поля не могут иметь модификатор const. В противном случае мы получим ошибку компиляции с текстом, который, к сожалению, не особо соответствует проблеме: "запрещено для объектов с защищенными членами или наследованием".
Конструктор класса заполняет поле x случайным числом, чтобы потом можно было наглядно увидеть его очистку функцией ZeroMemory. Метод print выводит содержимое всех полей для анализа, включая и уникальный номер (дескриптор) объекта — &this.
MQL5 не запрещает "натравить" ZeroMemory на переменную указатель:
Base *base = new Base();
|
Однако так делать не следует, потому что функция обнуляет только саму переменную base и, если она ссылалась на объект, тот останется "висеть" в памяти, недоступным из программы из-за утраты указателя.
Обнулять указатель можно только после того, как указываемый экземпляр освобожден с помощью оператора delete. Причем отдельный указатель из вышеприведенного примера, как и любую другую простую переменную (несоставную), проще сбросить оператором присваивания. ZeroMemory имеет смысл применять для составных объектов и массивов.
Функция позволяет работать с объектами иерархии классов. Например, мы можем описать производный от Base класс Dummy:
class Dummy : public Base
|
Он включает поля с динамическим массивом типа double, строку и указатель типа Base (это тот же тип, от которого класс наследован, но он здесь использован только для демонстрации проблем с указателем, чтобы не описывать еще один фиктивный класс). Когда функция ZeroMemory обнуляет объект Dummy, объект по указателю pointer теряется и не может быть освобожден в деструкторе. В результате, это приводит после завершения скрипта к появлению предупреждений об утечке памяти в оставшихся объектах.
ZeroMemory используется в OnStart для очистки массива объектов Dummy:
void OnStart()
|
В журнал будет выведено примерно следующее (начальное состояние будет отличаться, т.к. оно выводит содержимое "грязной", только что выделенной памяти; здесь приведен небольшой фрагмент):
Initial state
|
Для сравнения состояния объектов до и после очистки пользуйтесь дескрипторами.
Таким образом, одиночный вызов ZeroMemory способен сбросить состояние произвольной разветвленной структуры данных (массивов, структур, массивов структур с вложенными полями-структурами и массивами).
Наконец, посмотрим, как ZeroMemory может решить проблему инициализации массива строк. Функции ArrayInitialize и ArrayFill не работают со строками.
string text[LIMIT] = {};
|
В закомментированных инструкциях компилятор выдал бы ошибки, смысл которых в том, что тип string не поддерживается в данных функциях.
Выходом является функция ZeroMemory.