- Способы хранения информации: текстовый и двоичный
- Запись и чтение файлов в упрощенном режиме
- Открытие и закрытие файлов
- Управление дескрипторами файлов
- Выбор кодировки для текстового режима
- Запись и чтение массивов
- Запись и чтение структур (бинарные файлы)
- Запись и чтение переменных (бинарные файлы)
- Запись и чтение переменных (текстовые файлы)
- Управление позицией внутри файла
- Получение свойств файла
- Принудительная запись кэша на диск
- Удаление и проверка на существование файла
- Копирование и перемещение файлов
- Поиск файлов и папок
- Работа с папками
- Диалог выбора файла или папки
Управление дескрипторами файлов
Необходимость постоянно помнить об открытых файлах и освобождать локальные дескрипторы при любом выходе из функций, приводит к мысли поручить всю рутину специальным объектам.
Данный принцип хорошо известен в программировании и называется инициализацией с захватом ресурса (RAII, Resource Acquisition Is Initialization). Использование RAII упрощает контроль за ресурсами и обеспечивает их корректное состояние. В частности, это особенно эффективно, если выход из функции, где открывается файл (и для него создается объект-владелец), производится из нескольких разных мест.
Область применения RAII не ограничивается файлами. В разделе Шаблоны объектных типов мы создали класс AutoPtr, который управляет указателем на объект и является другим примером данной концепции. Ведь указатель — это тоже ресурс (память), и его очень просто "потерять" и накладно освобождать в нескольких разных ветках алгоритма.
Класс-обертка для файлов может быть полезен и с другой точки зрения. Одно из упущений файлового API заключается в отсутствии функции, которая позволила бы по дескриптору получить имя файла (несмотря на то, что такая связь, безусловно, существует внутри). Вместе с тем, внутри объекта мы можем сохранить это имя и осуществить собственную его привязку к дескриптору.
В простейшем случае нам нужен некий класс, который хранит в себе файловый дескриптор и автоматически закрывает его в деструкторе. Пример реализации показан в файле FileHandle.mqh.
class FileHandle
|
Два конструктора, а также перегруженный оператор присваивания обеспечивают привязку объекта к файлу (дескриптору). Второй конструктор позволяет передать ссылку на локальную переменную (из вызывающего кода), в которую дополнительно попадет новый дескриптор. Это будет своего рода внешний алиас того же дескриптора, который можно будет привычным образом использовать в других вызовах функций.
Но можно обойтись и без алиаса. Для этих случаев в классе определен оператор '~', возвращающий значение внутренней переменной handle.
int operator~() const
|
Наконец, самое главное, ради чего класс и был задуман, — "умный" деструктор:
~FileHandle()
|
В нем, после нескольких проверок, вызывается FileClose для контролируемой переменной handle. Дело в том, что файл может быть закрыт явным образом в другом месте программы, хотя это больше и не требуется при наличии данного класса. В результате, дескриптор может стать недействительным к моменту вызова деструктора, когда исполнение алгоритма покинет блок, в котором определен объект FileHandle. Для выяснения этого обстоятельства используется фиктивный вызов функции FileGetInteger: фиктивный, потому что он ничего полезного не делает. Если после вызова код внутренней ошибки остался равным 0, дескриптор рабочий.
В принципе, всех этих проверок можно не делать и написать просто:
~FileHandle()
|
Если дескриптор испорчен, FileClose не станет "возмущаться". Но мы добавили проверки, чтобы иметь возможность вывести диагностическую информацию.
Попробуем класс FileHandle в действии. Тестовый скрипт для него называется FileHandle.mq5.
const string dummy = "MQL5Book/dummy";
|
Согласно выводу в журнал, всё действует по плану:
FileHandle::~FileHandle: Automatic close for handle: 2
|
Однако, если файлов много, создание под каждый из них собственного экземпляра следящего объекта может стать неудобством. Для таких ситуаций имеет смысл спроектировать единый объект, собирающий "под свое крыло" все дескрипторы в заданном контексте (например, внутри функции).
Такой класс реализован в файле FileHolder.mqh и демонстрируется в скрипте FileHolder.mq5. Один экземпляр FileHolder сам создает по запросу вспомогательные объекты-наблюдатели класса FileOpener, который имеет общие черты с FileHandle, в особенности, деструктор, а также поле handle.
Для открытия файла через FileHolder следует использовать его метод FileOpen (его сигнатура повторяет сигнатуру стандартной функции FileOpen).
class FileHolder
|
Все объекты FileOpener складываются в массиве files для отслеживания их времени жизни. Там же нулевыми элементами отмечаются моменты регистрации локальных контекстов (блоков кода), в которых создаются объекты FileHolder — за это отвечает конструктор FileHolder.
FileHolder()
|
Как мы знаем, в процессе выполнения программы, она заходит во вложенные блоки кода (вызывает функции). Если в них требуется управление локальными дескрипторами файлов, там следует описать объекты FileHolder (по одному на блок или реже). Все такие описания по правилам стека (первым пришел, последним ушел) складываются в files и затем освобождаются в обратном порядке по мере того, как программа покидает контексты. В каждый такой момент вызывается деструктор.
~FileHolder()
|
Его задача — удалить последние объекты FileOpener в массиве вплоть до первого встреченного нулевого элемента, который означает границу контекста (далее в массиве идут дескрипторы из другого, внешнего контекста).
В полном объеме данный класс оставлен для самостоятельного изучения.
Посмотрим на его применение в тестовом скрипте FileHolder.mq5. Помимо функции OnStart здесь имеется функция SubFunc, и в обоих контекстах ведется работа с файлами.
const string dummy = "MQL5Book/dummy";
|
Мы не закрыли ни один дескриптор вручную, экземпляры FileHolder сделают это автоматически в деструкторах.
Вот пример вывода в журнал:
OnStart enter
|