Доступ к анонимным каналам c использованием итератора ostream_iterator
Канал можно также испо л ьзовать с итераторами ostream_iterator и istream_ iterator, которые представляют собой обобщенные объектно-ориентированные указатели. Итератор ostream_iterator позволяет передавать через канал целые контейнеры (т.е. списки, векторы, множества, очереди и пр.). Без использования iostreamo6beKTOB и итератора ostream_iterator передача контейнеров объектов была бы очень громоздкой и подверженной ошибкам процедурой. Операции, которые доступны для классов ostream_iterator и istream_iterator, перечислены в табл. 11.4.

Таблица»11.4. Операции, доступныедля классов ostream_iterator и istream_iterator
istream_iterator
а == b отношение эквивалентности
а != b отношение неэквивалентности
*a разыменовывание
++r инкремент (префиксная форма)
r++ инкремент (постфиксная форма)
ostream_iterator
++r инкремент (префиксная форма)
r++ инкремент (постфиксная форма)
Обычно эти итераторы используются вместе с iostreams-классами и стандартными алгоритмами. Итератор ostream_iterator предназначен только для последовательно выполняемой записи. После доступа к некоторому элементу программист не может вернуться к нему опять, не повторив всю итерацию сначала. При использовании этих итераторов канал обрабатывается как последовательный контейнер. Это означает, что при связывании канала с iostreams-объектами посредством итератора ostream_iterator и файловых дескрипторов мы можем применить стандартный алгоритм обработки данных для ввода их из канала и вывода их в канал. Причина того, что эти итераторы можно использовать вместе с каналами, состоит в связи, которая существует между итераторами и iostreams-классами. На рис. 11.10 представлена диаграмма, отображающая отношения между итераторами ввода-вывода и iostreams-классами.

На рис. 11.10 также показано, как эти классы взаимодействуют с объектно-ориентированным каналом. Рассмотрим подробнее, как итератор ostream_iterator используется с объектом класса ostream. Если инкрементируется указатель, мы ожидаем, что он будет указывать на следующую область памяти. Если же инкрементируется итератор ostream_iterator, он переме щ ается на следующую позицию выходного потока. Присваивал значение разыменованному указателю, мы тем самым помещаем это значение в область, на которую он указывает. Присваивал значение итератору ostream_iterator, мы помещаем это значение в выходной поток. Если выходной поток связан с объектом cout, это значение отобразится на стандартном устройстве вывода. Мы можем объявить объект класса ostream_iterator следующим образом, ostream_iterator<int> X(cout, « »);
Тогда X является объектом типа ostream_iterator. При выполнении операции инкремента X++; итератор X перейдет к слелую щ ей позиции выходного потока. А при выполнении этой инструкции присваивания
*X = Y;
значение Y будет отображено на стандартном устройстве вывода. Дело в том, что оператор присваивания "=" перегружен дл я использования объекта класса ostream. В результате объявления
ostream_iterator<int> X(cout, « »);
будет создан объект X с использованием аргумента cout. Второй аргумент в конструкторе является разделителем, который автоматически будет размещаться после каждого int -значения, вставляемого в поток данных. Объявление итератора ostream_iterator выглядит следующим образом (листинг 11.22).
// Листинг 11.22. Объявление класса ostream_iterator
template <class _Tp> class ostream_iterator {
protected:
ostream* _M_stream;
const char* _M_string; public:
typedef output_iterator_tag iterator_category;
typedef void value_type;
typedef void difference_type;
typedef void pointer;
typedef void reference;
ostream_iterator(ostream& _s) : _M_stream(&_s),_M_string(0) {}
ostream_iterator(ostream& _s, const char* _с): _M_s tream (&_s) , _M_string (_с) { }
ostream_iterator<_Tp>& operator=(const _Tp& _value) {
*_M_stream << _value;
if (_M_string){
*_M_stream << _M_string;
return *this;
}
ostream_iterator<_Tp>& operator*() { return *this; }
ostream_iterator<_Tp>& operator++() { return *this; }
ostream_iterator<_Tp>& operator++(int) { return *this; }
};
Конструктор класса ostream_iterator принимает ссылку на объект класса ostream. Класс ostream_iterator находится с классом ostream в отношении агрегирования. Назначение класса istream_iterator прямо противоположно классу ostream_iterator. Он используется с объектами класса istream (а не с объектами класса ostream). Если объекты классов istream_iterator и ostream_iterator связаны с iostream-объектами, которые в свою очередь связаны с файловыми дескрипторами канала, то при каждом инкрементировании итератора типа istream_iterator из канала будут считываться данные, а при каждом инкрементировании итератора типа ostream_iterator в канал будут записываться данные. Чтобы продемонстрировать, как эти компоненты работают вместе, рассмотрим две программы (11.2 и 11.2.1), в которых используются анонимные каналы связи. Про-грамма11.2 представляет родительский процесс, а программа11.2.1— сыновний. В»родительской» части для создания сыновнего процесса используются системные функции fork() и execl (). При том, что файловые дескрипторы наследуются сыновним процессом, их значения незамедлительно становятся достоянием программы 11.2.1 благодаря вызовуфункции execl() .
// Программа 11.2
10 int main(int argc, char *argv[])
11 {
12
13 int Size,Pid,Status,Fdl[2],Fd2[2];
14 pipe(Fdl); pipe(Fd2);
15 strstream Buffer;
16 char Value[50];
17 float Data;
18 vector<float>X(5,2.1221), Y;
19 Buffer « Fdl[0] « ends;
20 Buffer » Value;
21 setenv(«Fdin»,Value,l);
22 Buffer.clear();
23 Buffer « Fd2[l] « ends;
24 Buffer » Value;
25 setenv(«Fdout»,Value,l);
26 Pid = fork();
27 if(Pid != 0){
28 ofstream OPipe;
29 OPipe.attach(Fdl[l] ) ,-
30 ostream_iterator<float> OPtr(OPipe," »);
31 OPipe « X.size() « endl;
32 copy(X.begin(),X.end(),OPtr);
33 OPipe « flush;
34 ifstream IPipe;
35 IPipe.attach(Fd2[0]);
36 IPipe » Size;
37 for(int N = 0; N < Size;N++)
38 {
39 IPi ре » Data;
40 Y.push_back(Data);
41 }
42 wait(&Status);
43 ostream_iterator<float> OPtr2(cout," »);
44 copy(Y.begin(),Y.end(),OPtr2);
45 OPipe.close();
46 IPipe.close();
47 }
48 else{
49 execl("./programll-2b»,«programll-2b»,NULL);
50 } 51
52 return(0);
53 }
В строках 21 и 25 системнал функция setenv () используется для передачи значений файловых дескрипторов сыновнему процессу. Это возможно благодаря тому, что сыновний процесс наслелует среду родительского процесса. Мы можем устанавливать переменные среды в программе с помощью вызова функции setenv (). В данном случае мы устанавливаем их следующим образом.
Fdin=filedesc; Fdout=filedesc;
Сыновний процесс затем использует системный вызов getenv( ) для считывания значений переменных Fdin и Fdout. Значение переменной Fdin будет представлять «считывающий конец» канала для сыновнего процесса, а значение переменной Fdout — «записывающий». Использование системных функций setenv () и getenv() обеспечивает просгую форму межпроцессного взаимодействия (interprocess communication — IPC) между родительским и сыновним процессами. Каналы создаются при выполнении инструкций, приведенных в строке 14. Родительский процесс присоединяется к одному концу канала для операции записи с помощью метода attach() (строка29). После присоединения любые данные, помещенные в объект OPipe типа ofstream, будут записаны в канал. Итератор типа ostream_iterator подключается к объекгу OPipe при выполнении следующей инструкции (строка 30):
ostream_iterator<float> OPtr(OPipe," »);
Теперь итератор OPtr ссылается на объект OPipe. После каждой порции помещаемых в канал данных будет вставляться разделитель " ». С помощью итератора OPtr мы можем поместить в канал любое количество float -значений. При этом мы можем связать с каналом несколько итераторов различных типов. Но в этом случае необходимо, чтобы на «считывающем» конце канала данные извлекались с использованием ите раторов соответствующих типов. При выполнении слелующей инструкции из программы 11.2 в канал сначала помещается количество элементов, подлежащих передаче: OPipe « X.size() « endl;
Сами элементы отправляются с использованием одного из стандартных С++-алгоритмов:
copy(X.begin() ,X.end() ,OPtr) ;
Алгоритм copy () копирует содержимое одного контейнера в контейнер, связанный с итератором приемника. Здесь итератором приемника является объект OPtr. Объект OPtr связан с объектом OPipe, поэтому при выполнении алгоритма copy () («уместившегося» в одной строке кода) в канал переписывается все содержимое контейнера. Этот пример демонстрирует возможность использования стандартных алгоритмов для организации взаимодействия между различными частями сред параллельного или распределенного программирования. В данном случае алгоритм copy () пересылает информацию от одного процесса другому (из одного адресного пространства в другое). Эти процессы выполняются параллельно, и алгоритм copy () значительно упрощает взаимодействие между ними. Мы подчеркиваем важность этого подхода, поскольку, если есть хоть какал-то возможность упростить логику параллельной или распределенной программы, ею нужно непременно воспользоваться. Ведь межпроцессное взаимодействие — это один из самых сложных разделов параллельного или распределенного программирования. С++-алгоритмы, библиотека классов iostreamS и итератор типа ostream_iterator как раз и позволяют понизить уровень сложности разработки таких программ. Использование манипулятора flush (в строке 33) гарантирует прохождение данных по каналу.
В программе 11.2.1 сыновний процесс сначала получает количество объектов, принимаемых от канала (в строке 36), а затем для считывания самих объектов использует объект IPipe класса istream.
// Программа 11.2.1
11 class multiplier{
12 float X;
13 public:
14 multiplier(float Value) { X = Value;}
15 float &operator()(float Y) { X = (X * Y);return(X);}
16 }; 17
18
19 int main(int argc,char *argv[])
20 {
21 char Value[50] ;
22 int Fd[2] ;
23 float Data;
24 vector<float> X;
25 int NumElements;
26 multiplier N(12.2);
27 strcpy(Value,getenv(«Fdin»));
28 Fd[0] = atoi(Value);
29 strcpy(Value,getenv(«Fdout»));
30 Fd[l] = atoi(Value);
31 ifstream IPipe;
32 ofstream OPipe;
33 IPipe.attach(Fd[0]) ;
34 OPipe.attach(Fd[l]) ;
35 ostream_iterator<float> OPtr(OPipe," »);
36 IPipe » NumElements;
37 for(int N = 0;N < NumElements;N++)
38 {
39 IPipe » Data;
40 X.push_back(Data);
41 }
42 OPipe « X.size() « endl;
43 transform(X.begin(),X.end(),OPtr,N);
44 OPipe « flush;
45 return(0); 46
47 }
Сыновний процесс считывает элементы данных из канала, помещает их в вектор, азатем выполняет математические преобразования над каждым элементом вектора, после чего отправляет их назад родительскому процессу. Математические преобразования (строка43) выполняются с использованием стандартного С++-алгоритма transform и пользовательского класса multiplier. Алгоритм transform применяет к каждому элементу контейнера операцию, а затем результат этой операции помещает в контейнер-приемник. В данном случае контейнером-приемником служит объект Optr, который связан с объектом OPipe. Заголовки, которые необходимо включить в программу 11.2.1, приведены в разделе «Профиль программы 11.2.1».
Профиль программы 11.2.1
Имя программы program11-2b.cc
Описа н ие Программа представляет собой код сыновнего процесса, который запускается npoграммой 11.2. В этой программе для получения содержимого контейнера, отправленного из программы 11.2, используется объект класса ifstream. Для отправки через канал обработанной информации родительскому процессу в программе исполь-|зуется объект класса ostream_iterator и стандартный алгоритм transform.
Требуемые заголовки
<iostream>, algorithm>, <fstream>, <vector>, <iterator>, <stdlib.h>, |<string.h>, <unistd.h>.
Инструкции no компиляции и компоновке программ
с++ -o»programll-2b programll-2b.ee
Инструкции по выполнению [Эта программа запускается программой 11.2.
Несмотря на то что классы библиотеки iostream, итераторы типа istream_iterator и ostream_iterator упрощают программирование канала, они не изменяют его поведение. По-прежнему остаются в силе вопросы блокирования и проблемы, связанные с корректным порядком открытия и закрытия каналов, рассмотренные в главе 5. Но использование основных механизмов тех же методов объектно-ориентированного программирования все же позволяет понизить уровень сложности параллельного и распределенного программирования.
Больше книг — больше знаний!
Заберите 30% скидку новым пользователям на все книги Литрес с нашим промокодом
ПОЛУЧИТЬ СКИДКУ