При задании операций ввода/вывода мы никак не касались типов файлов, но ведь не все устройства можно рассматривать одинаково с точки зрения стратегии буферизации. Например, для ostream, подключенного к символьной строке, требуется буферизация другого вида, нежели для ostream, подключенного к файлу. С этими проблемами можно справиться, задавая различные буферные типы для разных потоков в момент инициализации (обратите внимание на три конструктора класса ostream). Есть только один набор операций над этими буферными типами, поэтому в функциях ostream нет кода, их различающего. Однако функции, которые обрабатывают переполнение сверху и снизу, виртуальные. Этого достаточно, чтобы справляться с необходимой в данное время стратегией буферизации. Это также служит хорошим примером применения виртуальных функций для того, чтобы сделать возможной однородную обработку логически эквивалентных средств с различной реализацией. Описание буфера потока в выглядит так:
struct streambuf { // управление буфером потока char* base; // начало буфера char* pptr; // следующий свободный char char* qptr; // следующий заполненный char char* eptr; // один из концов буфера char alloc; // буфер, выделенный с помощью new // Опустошает буфер: // Возвращает EOF при ошибке и 0 в случае успеха virtual int overflow(int c =EOF); // Заполняет буфер // Возвращет EOF при ошибке или конце ввода, // иначе следующий char virtual int underflow(); int snextc() // берет следующий char { return (++qptr==pptr)? underflow(): *qptr&0377; } //... int allocate() // выделяет некоторое пространство буфера streambuf() { /*... */} streambuf(char* p, int l) { /*... */} ~streambuf() { /*... */} };Обратите внимание, что здесь определяются указатели, необходимые для работы с буфером, поэтому обычные посимвольные действия можно определить (только один раз) в виде максимально эффективных inline- функций. Для каждой конкретной стратегии буферизации необходимо определять только функции переполнения overflow() и underflow(). Например:
|
|