Шаг 517.
Библиотека STL. Ввод-вывод с использованием потоковых классов. Классы потоковых буферов. Пользовательские буферы вывода

    На этом шаге мы рассмотрим особенности создания пользовательских буферов вывода.

    Для буфера, используемого для записи символов, поддерживаются три указателя, которые могут быть получены функциями pbase(), pptr() и epptr() (рисунок 1):


Рис.1. Интерфейс буферов вывода

    Символы в интервале от pbase() до pptr() (не включая символ, на который ссылается pptr()) уже записаны, но еще не выведены в соответствующий канал вывода.

    Символы записываются в буфер функцией sputc(). Символ копируется в текущую позицию записи (при наличии свободной позиции), после чего указатель на текущую позицию записи увеличивается. Если буфер полон (pptr()=epptr()), то содержимое буфера вывода посылается в соответствующий канал вывода вызовом виртуальной функции overflow(). Эта функция фактически отвечает за непосредственную передачу символов некоторому "внешнему представлению" (которое на самом деле может быть внутренним, как в случае со строковыми потоками данных). Реализация overflow() в базовом классе basic_streambuf возвращает только признак конца файла, означающий, что дальнейшая запись символов невозможна.

    Функция sputn() позволяет записать сразу несколько символов. Она перепоручает работу виртуальной функции xsputn(), которая может оптимизироваться для более эффективной записи нескольких символов. Реализация xsputn() в классе basic_streambuf вызывает sputc() для каждого символа, поэтому в ее переопределении нет абсолютной необходимости. Тем не менее в некоторых случаях запись нескольких символов реализуется более эффективно, чем последовательная запись отдельных символов, а функция xsputn() помогает оптимизировать обработку символьных последовательностей.

    Запись в потоковый буфер может выполняться и без буферизации - вместо этого символы выводятся сразу же после их получения. В этом случае указателям буфера вывода присваивается значение 0 или NULL. Конструктор по умолчанию делает это автоматически.

    На основе изложенного материала был разработан следующий пример потокового буфера, не использующего буферизацию. То есть для каждого символа вызывается функция overflow(). Остается лишь реализовать эту функцию.

// outbuf1.hpp
#include <streambuf>
#include <locale>
#include <cstdio>

class outbuf : public std::streambuf
{
  protected:
    // Главная функция вывода
    //  - вывод символов в верхнем регистре
    virtual int_type overflow (int_type c) {
        if (c != EOF) {
            // Преобразование символа к верхнему регистру
            c = std::toupper(c,getloc());

            // Запись символа в стандартный вывод
            if (putchar(c) == EOF) {
                return EOF;
            }
        }
        return c;
    }
};

    В данном случае каждый символ, передаваемый в потоковый буфер, записывается функцией putchar() языка С. Но перед выводом символ преобразуется к верхнему регистру функцией toupper(). Функция getloc() возвращает объект локального контекста, связанный с потоковым буфером.

    В представленном примере буфер вывода реализован специально для типа char (streambuf - специализация basic_streambuf для типа символов char). При использовании другого типа символов эту функцию следует реализовать с применением класса трактовок символов. В этом случае сравнение с с концом файла выполняется иначе. Вместо EOF должно возвращаться значение traits_eof(), а если аргумент с равен EOF, следует возвращать traits::not_eof(c) (где traits - второй аргумент шаблона basic_streambuf). Возможная реализация выглядит так:

#include <streambuf>
#include <locale>
#include <cstdio>

template <class charT, class traits = std::char_traits<charT> >
class basic_outbuf : public std::basic_streambuf<charT,traits>
{
  protected:
    // Главная функция вывода
    // - вывод символов в верхнем регистре
    virtual typename traits::int_type overflow (typename traits::int_type c) {
        if (!traits::eq_int_type(c,traits::eof())) {
            // Преобразование символа к верхнему регистру
            c = std::toupper(c,getloc());

            // Запись символа в стандартный вывод
            if (putchar(c) == EOF) {
                return traits::eof();
            }
        }
        return traits::not_eof(c);
    }
};

typedef basic_outbuf<char>    outbuf;
typedef basic_outbuf<wchar_t> woutbuf;

    Пусть этот потоковый буфер используется в представленной ниже программе:

//---------------------------------------------------------------------------

#include <vcl.h>
#include <iostream>
#include "outbuf1.hpp"

#include <conio.h>   //необходимо для getch()

#pragma hdrstop

//---------------------------------------------------------------------------

#pragma argsused
using namespace std;

std::string ToRus(const std::string &in)
{
  char *buff = new char [in.length()+1];
  CharToOem(in.c_str(),buff);
  std::string out(buff);
  delete [] buff;
  return out;
}

int main (int argc, char* argv[])
{
  outbuf ob;                // Создание специального буфера вывода
  std::ostream out(&ob);    // Инициализация выходного потока 
                            // созданным буфером вывода
  out << ToRus("31 в шестнадцатеричной системе счисления: ") 
      << std::hex << 31 << std::endl;


  getch();
  return 0;
}

//---------------------------------------------------------------------------
Текст этого примера можно взять здесь.

    В этом случае будет получен следующий результат:


Рис.2. Результат работы приложения

    Аналогичный подход может использоваться при записи в другие приемники. Так, для инициализации объекта конструктору потокового буфера можно передать дескриптор файла, имя сокетного соединения или два других потоковых буфера, используемые для одновременной записи. Чтобы организовать вывод в соответствующий приемник, достаточно реализовать функцию overflow(). Кроме того, функцию xsputn() следует реализовать для оптимизации вывода в потоковый буфер.

    На следующем шаге мы продолжим изучение этого вопроса.




Предыдущий шаг Содержание Следующий шаг