Блок задач

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

Сложность 3

Задача «Временный файловый поток»

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

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

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

//TempFileStream.hpp

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

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

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


class TempFileStream final
{
public:
    TempFileStream();
    ~TempFileStream();

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

    bool isOpen();

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

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

public:
    TempFileStream& open();
    void close();

public:
    size_t count();

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

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

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

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

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

public:
    TempFileStream& flush();

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

Примечания

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

  2. Поток предоставляет доступ к временному бинарному файлу (с гарантированно уникальным именем) в режиме одновременного чтения и записи. Причем доступ к содержимому файла должен происходить побайтово.

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

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

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

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

if ( tempFile.read(buffer, 10).count() == 10 )
  std::cout << "reading ok";
if ( tempFile.open().good() )
  std::cout << "file is successfully opened";
// и так тоже работает, но лучше так не делать
int i;
char s[20];
bool good = tempFile
  .open()
  .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";

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

  • TempFileStream() — конструктор объекта потока, который пытается открыть временный бинарный файл с гарантированно уникальным именем в режиме одновременной записи и чтения (см. примечание 2).
  • ~TempFileStream() — деструктор объекта потока. Если поток был связан с временным файлом, то сбрасывает буфера ввода/вывода, закрывает файл и удаляет временный файл.
  • TempFileStream(const TempFileStream &) = delete — удалённый конструктор копий (см. примечание 3).
  • operator=(const TempFileStream &) = delete — удалённый оператор копирования (см. примечание 3).
  • isOpen() — метод проверяет, связан ли данный поток каким-либо файлом.
  • state() — метод возвращает набор IOState флагов, описывающих текущее состояние потока (см. примечание 4).
  • clearState() — метод позволяет принудительно сбросить флаги состояния потока.
  • good() — метод проверяет, что у потока сброшены все индикаторы ошибок.
  • eof() — метод проверяет, что у потока выставлен индикатор достижения конца файла.
  • bad() — метод проверяет, что у потока выставлен индикатор ошибки ввода / вывода.
  • open() — привязывает поток к новому временному файлу с гарантированно уникальным именем в требуемом режиме (см. примечание 2). Если объект до этого был привязан к другому файлу, то предыдущий файл корректно закрывается и удаляется (см. примечание 3). Метод возвращает ссылку на самого себя (см. примечание 6). Последующий вызов state() позволяет определить успех / неуспех операции.
  • close() — закрывает файл и удаляет.
  • 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). Последующий вызов 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 методам, класса TempFileStream необходимо добавить в нужных местах спецификаторы static и const.
  2. Нельзя менять названия public и protected методов класса, а также типы и количество аргументов в них.
  3. Названия header и source файлов следующие: TempFileStream.hpp, TempFileStream.cpp.
  4. Написать unit test'ы для реализованных классов.