Бьерн Страуструп. Язык программирования С++



бет89/124
Дата16.07.2016
өлшемі3.27 Mb.
#204081
түріКнига
1   ...   85   86   87   88   89   90   91   92   ...   124

10.5.1 Закрытие потоков


Файл может быть закрыт явно, если вызвать close() для его потока:


mystream.close();
Но это неявно делает деструктор потока, так что явный вызов close()

может понадобиться, если только файл нужно закрыть до достижения

конца области определенности потока.

Здесь возникает вопрос, как реализация может обеспечить

создание предопределенных потоков cout, cin и cerr до их первого

использования и закрытие их только после последнего использования.

Конечно, разные реализации библиотеки потоков из могут

по-разному решать эту задачу. В конце концов, решение - это

прерогатива реализации, и оно должно быть скрыто от пользователя. Здесь

приводится только один способ, примененный только в одной реализации,

но он достаточно общий, чтобы гарантировать правильный порядок

создания и уничтожения глобальных объектов различных типов.

Основная идея в том, чтобы определить вспомогательный класс,

который по сути служит счетчиком, следящим за тем, сколько раз



был включен в раздельно компилировавшиеся программные

файлы:
class Io_init {

static int count;

//...


public:

Io_init();

^Io_init();

};
static Io_init io_init ;


Для каждого программного файла определен свой объект с именем io_init.

Конструктор для объектов io_init использует Io_init::count как первый

признак того, что действительная инициализация глобальных объектов

потоковой библиотеки ввода-вывода сделана в точности один раз:


Io_init::Io_init()

{

if (count++ == 0) {



// инициализировать cout

// инициализировать cerr

// инициализировать cin

// и т.д.

}

}
Обратно, деструктор для объектов io_init использует Io_count, как



последнее указание на то, что все потоки закрыты:
Io_init::^Io_init()

{

if (--count == 0) {



// очистить cout (сброс, и т.д.)

// очистить cerr (сброс, и т.д.)

// очистить cin

// и т.д.

}

}
Это общий прием работы с библиотеками, требующими инициализации и



удаления глобальных объектов. Впервые в С++ его применил Д. Шварц.

В системах, где при выполнении все программы размещаются в основной

памяти, для этого приема нет помех. Если это не так, то накладные

расходы, связанные с вызовом в память каждого программного файла

для выполнения функций инициализации, будут заметны. Как всегда,

лучше, по возможности, избегать глобальных объектов. Для классов,

в которых каждая операция значительна по объему выполняемой работы,

чтобы гарантировать инициализацию, было бы разумно проверять такие

первые признаки (наподобие Io_init::count) при каждой операции.

Однако, для потоков такой подход был бы излишне расточительным.



10.5.2 Строковые потоки


Как было показано, поток может быть привязан к файлу, т.е. массиву

символов, хранящемуся не в основной памяти, а, например, на диске. Точно

так же поток можно привязать к массиву символов в основной памяти.

Например, можно воспользоваться выходным строковым потоком ostrstream

для форматирования сообщений, не подлежащих немедленной печати:


char* p = new char[message_size];

ostrstream ost(p,message_size);

do_something(arguments,ost);

display(p);


С помощью стандартных операций вывода функция do_something может писать

в поток ost, передавать ost подчиняющимся ей функциям и т.п. Контроль

переполнения не нужен, поскольку ost знает свой размер и при заполнении

перейдет в состояние, определяемое fail(). Затем функция display может

послать сообщение в "настоящий" выходной поток. Такой прием наиболее

подходит в тех случаях, когда окончательная операция вывода

предназначена для записи на более сложное устройство, чем традиционное,

ориентированное на последовательность строк, выводное устройство.

Например, текст из ost может быть помещен в фиксированную область на экране.

Аналогично, istrstream является вводным строковым потоком,

читающим из последовательности символов, заканчивающейся нулем:
void word_per_line(char v[], int sz)

/*

печатать "v" размером "sz" по одному слову в строке



*/

{

istrstream ist(v,sz); // создать istream для v



char b2[MAX]; // длиннее самого длинного слова

while (ist>>b2) cout <

}
Завершающий нуль считается концом файла.

Строковые потоки описаны в файле .



10.5.3 Буферизация


Все операции ввода-вывода были определены без всякой связи с типом

файла, но нельзя одинаково работать со всеми устройствами без учета

алгоритма буферизации. Очевидно, что потоку ostream, привязанному к

строке символов, нужен не такой буфер, как ostream, привязанному к

файлу. Такие вопросы решаются созданием во время инициализации разных

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

операций над этими типами буферов, поэтому в ostream нет функций, код

которых учитывает различие буферов. Однако, функции, следящие за

переполнением и обращением к пустому буферу, являются виртуальными.

Это хороший пример применения виртуальных функций для единообразной

работы с эквивалентными логически, но различно реализованными

структурами, и они вполне справляются с требуемыми алгоритмами буферизации.

Описание буфера потока в файле может выглядеть следующим

образом:
class streambuf { // управление буфером потока

protected:

char* base; // начало буфера

char* pptr; // следующий свободный байт

char* gptr; // следующий заполненный байт

char* eptr; // один из указателей на конец буфера

char alloc; // буфер, размещенный с помощью "new"

//...


// Опустошить буфер:

// Вернуть EOF при ошибке, 0 - удача

virtual int overflow(int c = EOF);
// Заполнить буфер:

// Вернуть EOF в случае ошибки или конца входного потока,

// иначе вернуть очередной символ

virtual int underflow();

//...

public:


streambuf();

streambuf(char* p, int l);

virtual ~streambuf();
int snextc() // получить очередной символ

{

return (++gptr==pptr) ? underflow() : *gptr&0377;



}

int allocate(); // отвести память под буфер

//...

};
Подробности реализации класса streambuf приведены здесь только для



полноты представления. Не предполагается, что есть общедоступные

реализации, использующие именно эти имена. Обратите внимание на

определенные здесь указатели, управляющие буфером; с их помощью

простые посимвольные операции с потоком можно определить максимально

эффективно (и причем однократно) как функции-подстановки. Только

функции overflow() и underflow() требует своей реализации для каждого

алгоритма буферизации, например:
class filebuf : public streambuf {

protected:

int fd; // дескриптор файла

char opened; // признак открытия файла

public:

filebuf() { opened = 0; }



filebuf(int nfd, char* p, int l)

: streambuf(p,l) { /* ... */ }

~filebuf() { close(); }
int overflow(int c=EOF);

int underflow();


filebuf* open(char *name, ios::open_mode om);

int close() { /* ... */ }

//...

};

int filebuf::underflow() // заполнить буфер из "fd"



{

if (!opened || allocate()==EOF) return EOF;


int count = read(fd, base, eptr-base);

if (count < 1) return EOF;


gptr = base;

pptr = base + count;

return *gptr & 0377; // &0377 предотвращает размножение знака

}
За дальнейшими подробностями обратитесь к руководству по реализации

класса streambuf.



Достарыңызбен бөлісу:
1   ...   85   86   87   88   89   90   91   92   ...   124




©dereksiz.org 2024
әкімшілігінің қараңыз

    Басты бет