Personal tools
2.10 Synchronizing Streams
Click on the banner to return to the user guide home page.
2.10 Synchronizing Streams
In the previous section, we saw how streams can share stream buffers. In this section, we will see that streams can also share a file, as when streams in different processes exchange data through a file. Figure 20 graphically illustrates how streams share files:
Figure 20. Streams sharing a file
Because streams use a buffer, the content of the file might be different from the content of the buffer that is supposed to reflect the file's content. When data is extracted through a file stream, a certain part of the file's content is read into the buffer; subsequent extractions access the buffer instead of the file. Once the file content is modified, the buffer content becomes obsolete. Similarly, when data is written through a file stream, the output is stored in the buffer and not written to the file. The file is accessed only when the buffer is full. For this reason, output from one stream will not be immediately available to the other stream.
2.10.1 Explicit Synchronization
You can force the stream to empty its buffer into an output file, or to refill its buffer from an input file. This is done through the stream buffer's function pubsync(). Typically, you will call pubsync() indirectly through functions of the stream layer. Input streams and output streams have different member functions that implicitly call pubsync().
2.10.1.1 Output Streams
Output streams have a flush() function that writes the buffer content to the file. In case of failure, badbit will be set or an exception thrown, depending on the exception mask.
ofstream ofstr("/tmp/fil"); ofstr << "Hello "; \\1 ofstr << "World!\n"; ofstr.flush(); \\2
//1 | The attempt to extract anything from the file /tmp/fil after this insertion will probably fail, because the string "Hello " is buffered and not yet written to the external file. |
//2 | After the call to flush(), however, the file will contain "Hello World!\n". (Incidentally, the call to ostr.flush() can be replaced by the flush manipulator; i.e., ostr << flush;) |
Keep in mind that flushing is a time-consuming operation. The function flush()not only writes the buffer content to the file; it may also reread the buffer in order to maintain the current file position. For the sake of performance, you should avoid inadvertent flushing, as when the endl manipulator calls flush() on inserting the end-of-line character. (See Section 2.8.1.2.)
2.10.1.2 Input Streams
Input streams have a sync() function. It forces the stream to access the external device and refill its buffer, beginning with the current file position. The example below demonstrates the principle theoretically. In real-life, however, the two streams would belong to two separate processes and could use the shared file to communicate.
ofstream ofstr("/tmp/fil"); ifstream ifstr("/tmp/fil"); string s; ofstr << "Hello " ofstream::pos_type p = ofstr.tellp(); ofstr << "World!\n" << flush; ifstr >> s; \\1 ofstr.seekp(p); ofstr << "Peter!" << flush; \\2 ifstr >> s; \\3 ofstr << " Happy Birthday!\n" << flush; \\4 ifstr >> s; \\5 ifstr.sync(); \\6 ifstr >> s;
//1 | Here the input stream extracts the first string from the shared file. In doing so, the input stream fills its buffer. It reads as many characters from the external file as needed to fill the internal buffer. For this reason, the number of characters to be extracted from the file is implementation-specific; it depends on the size of the internal stream buffer. |
//2 | The output stream overwrites part of the file content. Now the file content and the content of the input stream's buffer are inconsistent. The file contains "Hello Peter!"; the input stream's buffer still contains "Hello World!". |
//3 | This extraction takes the string "World!" from the buffer instead of yielding "Peter!", which is the current file content. |
//4 | More characters are appended to the external file. The file now contains "Hello Peter! Happy Birthday!", whereas the input stream's buffer is still unchanged. |
//5 | This extraction yields nothing. The input stream filled its buffer with the entire content of the file because the file is so small in our toy example. Subsequent extractions made the input stream hit the end of its buffer, which is regarded as the end of the file as well. The extraction results in eofbit set, and nothing will be extracted. There is no reason to ever access the external file again. |
//6 | A call to sync() eventually forces the input stream to refill the buffer from the external device, beginning with the current file position. After the synchronization, the input stream's buffer will contain "Happy Birthday!\n". The next extraction will yield "Happy".
As the draft specifies the behavior of sync() as implementation-defined, you can alternatively try repositioning the input stream to the current position instead; i.e., istr.seekg(ios_base::cur); |
Please note: If you have to synchronize several streams that share a file, it is advisable to call the sync() function after each output operation and before each input operation.
2.10.2 Implicit Synchronization Using the unitbuf Format Flag
You can achieve a kind of automatic synchronization for output files by using the format flag ios_base::unitbuf. It causes an output stream to flush its buffer after each output operation as follows:
ofstream ostr("/tmp/fil"); ifstream istr("/tmp/fil"); ostr << unitbuf; \\1 while (some_condition) { ostr << "_ some output_"; \\2 // process the output istr >> s; // _ }
//1 | Set the unitbuf format flag. |
//2 | After each insertion into the shared file /tmp/fil, the buffer is automatically flushed, and the output is available to other streams that read from the same file. |
Since it is not overly efficient to flush after every single token that is inserted, you might consider switching off the unitbuf flag for a lengthy output that is not supposed to be read partially.
ostr.unsetf(ios_base::unitbuf); \\1 ostr << " _ some lengthy and complicated output _"; ostr.flush().setf(ios_base::unitbuf); \\2
//1 | Switch off the unitbuf flag. Alternatively, using manipulators, you can say ostr << nounitbuf; |
//2 | Flush the buffer and switch on the unitbuf flag again. Alternatively, you can say ostr << flush << unitbuf; |
2.10.3 Implicit Synchronization by Tying Streams
Another mechanism for automatic synchronization in certain cases is tying a stream to an output stream, as demonstrated in the code below. All input or output operations flush the tied stream's buffer before they perform the actual operation.
ofstream ostr("/tmp/fil"); ifstream istr("/tmp/fil"); ostream* old_tie = istr.tie(&ostr); //1 while (some_condition) { ostr << " some output "; string s; while (istr >> s) //2 // process input ; } istr.tie(old_tie); //3
//1 | The input stream istr is tied to the output stream ostr. The tie() function returns a pointer to the previously tied output stream, or zero if no output stream is tied. |
//2 | Before any input is done, the tied output stream's buffer is flushed so that the result of previous output operations to ostr is available in the external file /tmp/fil. |
//3 | The previous tie is restored. |
2.10.4 Synchronizing the Predefined Standard Streams
The predefined streams cin, cout, cerr, and clog are examples of synchronized streams:
cin is tied to cout; i.e., before each input operation on cin, the output stream cout is forced to flush its buffer.
cerr is synchronized using the unitbuf format flag; i.e., after each output to cerr, its buffer is flushed.
clog is connected to the same output channel and thus behaves like cerr, except that it is not synchronized with any of the other standard streams; i.e., it does not have the unitbuf flag set.
2.10.5 Synchronization with the C Standard I/O
The predefined C++ streams cin, cout, cerr, and clog are associated with the standard C files stdin, stdout, and stderr, as we saw in Section 2.3.1. This means that insertions into cout, for instance, go to the same file as output to stdout. By default, input and output to the predefined streams is synchronized with read or write operations on the standard C files. The effect is that input and output operations are executed in the order of invocation, independently of whether the operations used the predefined C++ streams or the standard C files.
This synchronization is time-consuming and thus might not be desirable in all situations. You can switch it off by calling:
sync_with_stdio(false);
After such a call, the predefined streams operate independently of the C standard files, with possible performance improvements in your C++ stream operations. However, you should call sync_with_stdio() prior to any input or output operation on the predefined streams, because otherwise the effect of calling sync_with_stdio() will be implementation-defined.
©Copyright 1996, Rogue Wave Software, Inc.