Блок задач

3. Простые классы

Сложность 3

Задача «Файловый поток»

Реализовать класс FileStream для работы с файлом как с потоком ввода/вывода. Класс представляет собой обёртку над набором функций f...(FILE * pf ...) стандартной библиотеки C.

Постановка задачи

Реализовать класс FileStream в соответствии с приведённым ниже интерфейсом.

//FileStream.hpp

#include <cstdint> // uint8_t
#include <cstdio>

enum OpenMode : uint8_t
{
    None = 0x00,
    Read = 0x01, // разрешает операции чтения из потока
    Write = 0x02, // разрешает операции записи в поток
    Binary = 0x04, // поток интерпретируется как поток байт, а не как поток символов
};

enum IOState : uint8_t
{
    Good = 0x00, // нет ошибок
    EoF = 0x01, // при чтении достигнут конец потока
    Bad = 0x02, // ошибка ввода-вывода
};

enum SeekDirection : int
{
    Begin = SEEK_SET,
    Current = SEEK_CUR,
    End = SEEK_END,
};


class FileStream final
{
public:
    FileStream();
    explicit FileStream(const char* name, uint8_t mode);
    ~FileStream();

public:
    // объекты класса stream нельзя копировать: 
    FileStream(const FileStream&) = delete;
    FileStream& operator=(const FileStream&) = delete;

public:
    // сообщает, связан ли данный экземпляр stream с каким-либо файлом
    bool isOpen();

public:
    uint8_t state();
    void clearState();

public:
    bool good();
    bool eof();
    bool bad();

public:
    FileStream& open(const char* name, uint8_t mode);
    void close();

public:
    size_t count();

public:
    FileStream& ignore(size_t n = 1, int delim = EOF);

public:
    FileStream& read(void* buffer, size_t size);
    FileStream& write(const void* buffer, size_t size);

public:
    int get();
    FileStream& get(char& c);
    FileStream& get(char* s, size_t len);
    FileStream& put(char c);
    FileStream& put(const char* s, size_t len);

public:
    FileStream& print(const char* format, ...);
    FileStream& scan(const char* format, ...);

public:
    fpos_t pos();
    FileStream& pos(fpos_t p);
    long int tell();
    FileStream& seek(long int offset, SeekDirection dir);

public:
    FileStream& flush();

private:
  // поля...
};

Примечания

  1. Для public и protected методов класса не допускается изменение названий, а также типов и количества аргументов. Однако, в приведённых фрагментах кода намеренно опущен ряд спецификаторов const и static. При реализации необходимо вернуть их на место в соответствии с логикой работы класса.

  2. Поток может предоставлять доступ к файлу в режиме чтения или записи. Доступ к содержимому файла может происходить посимвольно либо побайтово. Для описания всех этих режимов работы с файлом, создано перечисление OpenMode, в котором описаны соответствующие битовые флаги. Комбинирование этих флагов с помощью побитового ИЛИ позволяет гибко настраивать режим работы с файлом. Обратите внимание, что допускается режим открытия файла одновременно на чтение и на запись.

  3. Поток ввода / вывода должен быть однозначно связан с некоторым файлом. Ситуация, когда один объект класса FileStream связан с несколькими файлами — недопустима. Точно так же недопустима ситуация, когда с одним файлом связаны несколько объектов FileStream. Поэтому класс потока должен быть спроектирован так, чтобы предотвращать эти ситуации.

  4. Операции ввода / вывода в файл могут закончится неудачей, поэтому необходимы механизмы обнаружения ошибочных состояний. Один из таких механизмов — это флаги состояния потока. Стандартная библиотека C связывает с каждым файлом два индикатора ошибок: индикатор достижения конца файла, и индикатор всех остальных ошибок ввода-вывода. Для описания этих двух видов ошибок создано перечисление IOState. Поток может перейти в то или иное ошибочное состояние, и его функционал при этом будет ограничен. В классе FileStream предусмотрены методы проверки текущего состояния потока.

  5. При операциях ввода / вывода регулярно возникает необходимость знать количество записанных / прочитанных элементов потока (байтов, символов, и пр.). Однако добавление в каждый метод класса функционала для информирования о количестве элементов привело бы значительному усложнению и захламлению интерфейса класса. Поэтому с каждым объектом класса FileStream связан счётчик элементов, обновляемый после каждой операции ввода / вывода. Доступ к счётчику обеспечивается специальным методом count().

  6. Интерфейс класса спроектирован так, что бы при необходимости упростить немедленный вызов count(), state() и других методов. Большинство методов класса возвращают ссылку на объект FileStream, т.е. на тот же самый объект, от которого этот метод был вызван. Это позволяет организовывать цепочки вызовов:

if ( file.read(buffer, 10).count() == 10 )
  std::cout << "reading ok";
if ( file.open("name.ext", Write).good() )
  std::cout << "file is successfully opened";
// и так тоже работает, но лучше так не делать
int i;
char s[20];
bool good = file
  .open("name.ext", Read)
  .ignore(10, '|').scan("%d", &i)
  .ignore(10, '|').scan("%s", s)
  .ignore(10, '|').good()
if ( good )
  std::cout << "i'm lucky and have no idea why it works";

Описание интерфейса

  • FileStream() — конструктор но умолчанию. Создаёт поток, не привязанный ни к какому файлу.
  • FileStream(const char * name, uint8_t mode) — конструктор объекта потока, который пытается открыть файл с указанным именем в требуемом режиме (см. примечание 2).
  • ~FileStream() — деструктор объекта потока. Если поток был связан с файлом, то сбрасывает буфера ввода/вывода, закрывает файл и освобождает системные ресурсы.
  • FileStream(const FileStream &) = delete — удалённый конструктор копий (см. примечание 3).
  • operator=(const FileStream &) = delete — удалённый оператор копирования (см. примечание 3).
  • isOpen() — метод проверяет, связан ли данный поток каким-либо файлом.
  • state() — метод возвращает набор IOState флагов, описывающих текущее состояние потока (см. примечание 4).
  • clearState() — метод позволяет принудительно сбросить флаги состояния потока.
  • good() — метод проверяет, что у потока сброшены все индикаторы ошибок.
  • eof() — метод проверяет, что у потока выставлен индикатор достижения конца файла.
  • bad() — метод проверяет, что у потока выставлен индикатор ошибки ввода / вывода.
  • open(const char * name, uint8_t mode) — привязывает поток к файлу с указанным именем в требуемом режиме (см. примечание 2). Если объект до этого был привязан к другому файлу, то предыдущий файл корректно закрывается (см. примечание 3). Метод возвращает ссылку на самого себя (см. примечание 6). Последующий вызов state() позволяет определить успех / неуспех операции.
  • close() — закрывает файл и отвязывает его от объекта FileStream.
  • count() — метод сообщает о количестве элементов потока успешно прочитанных / заспинных во время последней операции ввода / вывода (см. примечание 5).
  • ignore(size_t n, int delim) — метод читает из файла элементы (символы или байты) никуда не сохраняя (игнорируя) их. Метод читает и игнорирует элементы до тех пор, пока не прочитает указанный элемент разделитель delim, либо пока не прочитает заданное количество элементов n. Файл должен быть открыт в режиме чтения. Метод возвращает ссылку на самого себя (см. примечание 6). Последующий вызов count() возвращает количество успешно считанных элементов.
  • read(void * buffer, size_t size) — метод считывает из файла заданное количество элементов size и сохраняет их в буфер buffer. Файл должен быть открыт в режиме чтения. Метод возвращает ссылку на самого себя (см. примечание 6). Последующий вызов count() возвращает количество успешно считанных элементов.
  • write(const void * buffer, size_t size) — метод записывает в файл size элементов из буфера buffer. Файл должен быть открыт в режиме записи. Метод возвращает ссылку на самого себя (см. примечание 6). Последующий вызов count() возвращает количество успешно записанных элементов.
  • get() — метод считывает из потока один элемент. Файл должен быть открыт в режиме чтения.
  • get(char & c) — метод считывает из потока один элемент и сохраняет по указанной ссылке c. Файл должен быть открыт в режиме чтения. Метод возвращает ссылку на самого себя (см. примечание 6). Последующий вызов count() возвращает количество успешно считанных элементов.
  • get(char * s, size_t len) — метод считывает строку из файла и сохраняет её в буффер s длинны len. Файл должен быть открыт в режиме чтения. Метод возвращает ссылку на самого себя (см. примечание 6). Последующий вызов count() возвращает количество успешно считанных элементов.
  • put(char c) — метод записывает в потока один элемент. Файл должен быть открыт в режиме записи. Метод возвращает ссылку на самого себя (см. примечание 6). Последующий вызов count() возвращает количество успешно записанных элементов.
  • put(const char * s, size_t len) — метод записывает в поток строку длинны len из буфера s. Файл должен быть открыт в режиме записи. Метод возвращает ссылку на самого себя (см. примечание 6). Последующий вызов count() возвращает количество успешно записанных элементов.
  • print(const char * format, ...) — метод осуществляет форматированный вывод в поток. Файл должен быть открыт в режиме записи. Метод возвращает ссылку на самого себя (см. примечание 6). Последующий вызов count() возвращает количество успешно записанных элементов.
  • scan(const char * format, ...) — метод осуществляет форматированное чтение из потока. Метод возвращает ссылку на самого себя (см. примечание 6). Файл должен быть открыт в режиме чтения. Метод возвращает ссылку на самого себя (см. примечание 6). Последующий вызов count() возвращает количество успешно распознанных и заполненных аргументов функции.
  • pos() — метод сообщает текущую позицию в файле в виде fpos_t.
  • pos(fpos_t p) — метод устанавливает текущую позицию в файле, используя значение p, полученное ранее при вызове метода pos(). Метод возвращает ссылку на самого себя (см. примечание 6). Последующий вызов state() позволяет определить успех / неуспех операции.
  • long int tell() — метод позволяет узнать смещение от начала файла для текущей позиции.
  • seek(long int offset, SeekDirection dir) — метод позволяет установить текущую позицию через смещение от начала/конца файла. Метод возвращает ссылку на самого себя (см. примечание 6). Последующий вызов state() позволяет определить успех / неуспех операции.
  • flush() — метод сбрасывает всё содержимое буферов в файл. Метод возвращает ссылку на самого себя (см. примечание 6). Последующий вызов state() позволяет определить успех / неуспех операции.

Требования

  1. К public методам, класса FileStream необходимо добавить в нужных местах спецификаторы static и const.
  2. Нельзя менять названия public и protected методов класса, а также типы и количество аргументов в них.
  3. Названия header и source файлов следующие: FileStream.hpp, FileStream.cpp.
  4. Написать unit test'ы для реализованных классов.