Создать класс, который ведет себя как поток, очень просто. Допустим, мы хотим создать такой класс с именем MyStream , определение класса будет таким простым, как:
#include <istream> // class "basic_iostream" is defined here
class MyStream : public std::basic_iostream<char> {
private:
std::basic_streambuf buffer; // your streambuf object
public:
MyStream() : std::basic_iostream<char>(&buffer) {} // note that ampersand
};
Конструктор вашего класса должен вызывать конструктор std::basic_iostream<char> с указателем на пользовательский объект std::basic_streambuf<char>. std::basic_streambuf — это просто класс шаблона, который определяет структуру буфера потока. Таким образом, вы должны получить свой собственный буфер потока. Вы можете получить его двумя способами:
- Из другого потока. В каждом потоке есть элемент
rdbuf, который не принимает аргументов и возвращает указатель на используемый им буфер потока. Пример:
...
std::basic_streambuf* buffer = std::cout.rdbuf(); // take from std::cout
...
- Создайте свой собственный. Вы всегда можете создать класс буфера, производный от
std::basic_streambuf<char>, и настроить его по своему усмотрению.
Теперь мы определили и реализовали класс MyStream, нам нужен буфер потока. Давайте выберем вариант 2 сверху и создадим собственный буфер потока и назовем его MyBuffer . Нам понадобится следующее:
- Конструктор для инициализации объекта.
- Непрерывный блок памяти для временного хранения вывода программы.
- Непрерывный блок памяти для временного хранения ввода пользователя (или чего-то другого).
- Метод
overflow , который вызывается, когда память, выделенная для хранения выходных данных, заполнена.
- Метод
underflow , который вызывается, когда программа считывает все входные данные и запрашивает дополнительные входные данные.
- Метод
sync , который вызывается при сбросе вывода.
Поскольку мы знаем, что нужно для создания класса буфера потока, давайте объявим его:
class MyBuffer : public std::basic_streambuf<char> {
private:
char inbuf[10];
char outbuf[10];
int sync();
int_type overflow(int_type ch);
int_type underflow();
public:
MyBuffer();
};
Здесь inbuf и outbuf — это два массива, которые будут хранить ввод и вывод соответственно. int_type — это специальный тип, похожий на char и созданный для поддержки нескольких типов символов, таких как char, wchar_t и т. д.
Прежде чем мы перейдем к реализации нашего класса буфера, нам нужно знать, как он будет работать.
Чтобы понять, как работают буферы, нам нужно знать, как работают массивы. Массивы — это не что иное, как указатели на непрерывную память. Когда мы объявляем массив char с двумя элементами, операционная система выделяет 2 * sizeof(char) памяти для нашей программы. Когда мы обращаемся к элементу массива с помощью array[n], он преобразуется в *(array + n), где n — номер индекса. Когда вы добавляете n к массиву, он переходит к следующему n * sizeof(<the_type_the_array_points_to>) (рис. 1). Если вы не знаете, что такое арифметика указателей, я бы порекомендовал вам изучить это, прежде чем продолжить. cplusplus.com имеет хорошая статья об указателях для начинающих.
array array + 1
\ /
------------------------------------------
| | | 'a' | 'b' | | |
------------------------------------------
... 105 106 107 108 ...
| |
-------
|
memory allocated by the operating system
figure 1: memory address of an array
Теперь, когда мы много знаем об указателях, давайте посмотрим, как работают буферы потока. Наш буфер содержит два массива inbuf и outbuf. Но как стандартная библиотека узнает, что ввод должен храниться в inbuf, а вывод должен храниться в outbuf? Итак, есть две области, называемые областью получения и областью ввода, которые являются областью ввода и вывода соответственно.
Область вывода задается следующими тремя указателями (рисунок 2):
pbase() или база путов: начало области путов
epptr() или конечный указатель: конец области ввода
pptr() или указатель: куда будет помещен следующий символ
На самом деле это функции, которые возвращают соответствующий указатель. Эти указатели устанавливаются setp(pbase, epptr) . После вызова этой функции pptr() устанавливается на pbase(). Чтобы изменить его, мы будем использовать pbump(n), который перемещает pptr() на n символов, n может быть положительным или отрицательным. Обратите внимание, что поток будет записывать в предыдущий блок памяти epptr(), но не epptr().
pbase() pptr() epptr()
| | |
------------------------------------------------------------------------
| 'H' | 'e' | 'l' | 'l' | 'o' | | | | | | |
------------------------------------------------------------------------
| |
--------------------------------------------------------
|
allocated memory for the buffer
figure 2: output buffer (put area) with sample data
Область получения задается следующими тремя указателями (рисунок 3):
eback() или конец назад, начало области получения
egptr() или конец указателя получения, конец области получения
gptr() или получить указатель, позицию, которая будет прочитана
Эти указатели устанавливаются функцией setg(eback, gptr, egptr). Обратите внимание, что поток будет читать предыдущий блок памяти egptr(), но не egptr().
eback() gptr() egptr()
| | |
------------------------------------------------------------------------
| 'H' | 'e' | 'l' | 'l' | 'o' | ' ' | 'C' | '+' | '+' | | |
------------------------------------------------------------------------
| |
--------------------------------------------------------
|
allocated memory for the buffer
figure 3: input buffer (get area) with sample data
Теперь, когда мы обсудили почти все, что нам нужно знать перед созданием пользовательского буфера потока, пришло время его реализовать! Мы попробуем реализовать наш потоковый буфер так, чтобы он работал как std::cout!
Начнем с конструктора:
MyBuffer() {
setg(inbuf+4, inbuf+4, inbuf+4);
setp(outbuf, outbuf+9);
}
Здесь мы устанавливаем все три указателя на одну позицию, что означает отсутствие читаемых символов, принудительно устанавливая underflow(), когда требуется ввод. Затем мы устанавливаем указатель put таким образом, чтобы поток мог записывать весь массив outbuf, кроме последнего элемента. Мы сохраним его для будущего использования.
Теперь давайте реализуем метод sync(), который вызывается при сбросе вывода:
int sync() {
int return_code = 0;
for (int i = 0; i < (pptr() - pbase()); i++) {
if (std::putchar(outbuf[i]) == EOF) {
return_code = EOF;
break;
}
}
pbump(pbase() - pptr());
return return_code;
}
Это делает его работу очень легко. Сначала он определяет, сколько символов нужно напечатать, затем печатает один за другим и перемещает pptr() (помещает указатель). Он возвращает EOF или -1, если символ любой символ является EOF, 0 в противном случае.
Но что делать, если область ввода заполнена? Итак, нам нужен метод overflow(). Давайте реализуем это:
int_type overflow(int_type ch) {
*pptr() = ch;
pbump(1);
return (sync() == EOF ? EOF : ch);
}
Ничего особенного, это просто помещает дополнительный символ в сохраненный последний элемент outbuf и перемещает pptr() (помещает указатель), а затем вызывает sync() . Он возвращает EOF, если sync() вернул EOF, иначе дополнительный символ.
Теперь все завершено, кроме обработки ввода. Давайте реализуем underflow() , который вызывается, когда все символы во входном буфере прочитаны:
int_type underflow() {
int keep = std::max(long(4), (gptr() - eback()));
std::memmove(inbuf + 4 - keep, gptr() - keep, keep);
int ch, position = 4;
while ((ch = std::getchar()) != EOF && position <= 10) {
inbuf[position++] = char(ch);
read++;
}
if (read == 0) return EOF;
setg(inbuf - keep + 4, inbuf + 4 , inbuf + position);
return *gptr();
}
Немного сложно понять. Давайте посмотрим, что здесь происходит. Во-первых, он вычисляет, сколько символов он должен сохранить в буфере (максимум 4), и сохраняет его в переменной keep. Затем он копирует последние keep числовых символов в начало буфера. Это сделано потому, что символы могут быть помещены обратно в буфер с помощью метода unget() std::basic_iostream . Программа может даже читать следующие символы, не извлекая их с помощью метода peek() из std::basic_iostream . После того, как последние несколько символов возвращены, он считывает новые символы, пока не достигнет конца входного буфера или не получит на входе EOF. Затем он возвращает EOF, если символы не прочитаны, в противном случае продолжается. Затем он перемещает все указатели на получение и возвращает первый прочитанный символ.
Поскольку наш потоковый буфер теперь реализован, мы можем настроить наш потоковой класс MyStream, чтобы он использовал наш потоковый буфер. Итак, мы меняем приватную переменную buffer:
...
private:
MyBuffer buffer;
public:
...
Теперь вы можете протестировать свой собственный поток, он должен принимать входные данные и отображать вывод из терминала.
Обратите внимание, что этот поток и буфер могут обрабатывать только ввод и вывод на основе char. Ваш класс должен быть производным от соответствующего класса для обработки других типов ввода и вывода (например, std::basic_streambuf<wchar_t> для широких символов) и реализовывать функции-члены или метод, чтобы они могли обрабатывать этот тип символов.
person
Akib Azmain
schedule
26.08.2020