Skip to content. Skip to navigation

ICTP Portal

Sections
You are here: Home Manuals on-line PGI Compiler pgC_lib stdlibug 2.9 Streams and Stream Buffers
Personal tools
Document Actions

2.9 Streams and Stream Buffers



Click on the banner to return to the user guide home page.

2.9 Streams and Stream Buffers

So far we have used streams as the source or target of input and output operations. But there is another aspect of streams: they are also objects in your C++ program that you might want to copy and pass around like other objects. However, streams cannot simply be copied and assigned. The following section shows what you have to do instead.

Stream buffers play a special role. In numerous cases you will not want to create copies of a stream buffer object, but simply share a stream buffer among streams. Section 2.9.2 explores this option.

If you really want to work with copies of stream buffers, see section 2.9.3 for hints and caveats.

2.9.1 Copying and Assigning Stream Objects

Stream objects cannot simply be copied and assigned. Let us consider a practical example to see what it means. A program writes data to a file if a file name is specified on program call, or to the standard output stream cout if no file name is specified. You want to write to one output stream in your program; this stream will either be a file stream or the standard output stream. The most obvious way to do this is to declare an output file stream object and assign it to cout, or to use cout directly. However, you can't do it this way:

int main(int argc, char argv[])
{
  ofstream fil;
  if (argc > 1)
  {  fil.open(argv[1]);
     cout = fil;                            // never do this !!!
  } 
  // output to cout, e.g. 
  cout << "Hello world!" << endl;
}

This solution is bad for at least two reasons. First, assignments to any of the predefined streams should be avoided. The predefined stream cin, cout, cerr, and clog have special properties and are treated differently from other streams. If you reassign them, as done with cout in the example above, you lose their special properties. Second, assignment and copying of streams is hazardous. The assignment of the output stream fil will compile and might even work; however, your program is likely to crash afterwards.


Please note: Stream objects must never be copied or assigned to each other.


Let us see why. The copy constructor and the assignment operator for streams are not defined; therefore, the compiler-generated default copy constructor and assignment operator are used. As usual, they only copy and assign a stream object's data members, which is insufficient because a stream object holds pointers as well. Figure 14 is a sketch of the data held by a stream object:

Figure 14. Data held by a stream object. Please note that some data members are omitted for the sake of simplicity.


The stream buffer contains several pointers to the actual character buffer it maintains. The default copy constructor and assignment operator will not correctly handle these pointers.

2.9.1.1 Copying a Stream's Data Members

To achieve the equivalent effect of a copy, you might consider copying each data member individually. This can be done as in the following example:

int main(int argc, char argv[])
{
  ofstream out;
  if (argc > 1)
     out.open(argv[1]);
  else
  {  out.copyfmt(cout);                                       //1
     out.clear(cout.rdstate());                               //2
     out.rdbuf(cout.rdbuf());                                 //3
  } 
  // output to out, e.g. 
  out << "Hello world!" << endl;
}
//1The copyfmt() function copies all data from the standard output stream cout to the output file stream out, except the error state and the stream buffer. (There is a function exceptions() that allows you to copy the exception mask separately; i.e., cout.exceptions(fil.exceptions());. However, you need not do this explicitly, since copyfmt() already copies the exception mask.)
//2Here the error state is copied.
//3Here the stream buffer pointer is copied.

Please note the little snag here. After the call to rdbuf(), the buffer is shared between the two streams, as shown in Figure 15:

Figure 15. Copying a stream's internal data results in a shared buffer


2.9.1.2 Sharing Stream Buffers Inadvertently

Whether or not you intend to share a stream buffer among streams depends on your application. In any case, it is important that you realize the stream buffer is shared after a call to rdbuf(); in other words, you must monitor the lifetime of the stream buffer object and make sure it exceeds the lifetime of the stream. In our little example above, we use the standard output stream's buffer. Since the standard streams are static objects, their stream buffers have longer lifetimes that most other objects, so we are safe. However, whenever you share a stream buffer among other stream objects, you must carefully consider the stream buffer's lifetime.

The example above has another disadvantage we haven't considered yet, as shown in the following code:

int main(int argc, char argv[])
{
  ofstream out;
  if (argc > 1)
     out.open(argv[1]);
  else
  {  out.copyfmt(cout);                                       //1
     out.clear(cout.rdstate());                               //2
     out.rdbuf(cout.rdbuf());                                 //3
  } 
  // output to out, e.g. 
  out << "Hello world!" << endl;
}

As we copy the standard output stream's entire internal data, we also copy its special behavior. For instance, the standard output stream is synchronized with the standard input stream. (See Section 2.10.4 for further details.) If our output file stream out is a copy of cout, it is forced to synchronize its output operations with all input operations from cin. This might not be desired, especially since synchronization is a time-consuming activity. Here is a more efficient approach using only the stream buffer of the standard output stream:

int main(int argc, char argv[])
{
  filebuf* fb = new filebuf;                                  //1
  ostream out((argc>1)?                                       //2
    fb->open(argv[1],ios_base::out|ios_base::trunc):          //3
    cout.rdbuf());                                            //4
  if (out.rdbuf() != fb)
    delete fb;
  out << "Hello world!" << endl;
}
//1Instead of creating a file stream object, which already contains a file buffer object, we construct a separate file buffer object on the heap that we can hand over to an output stream object if needed. This way we can delete the file buffer object if not needed. In the original example, we constructed a file stream object with no chance of eliminating the file buffer object if not used.
//2An output stream is constructed. The stream has either the standard output stream's buffer, or a file buffer connected to a file.
//3If the program is provided with a file name, the file is opened and connected to the file buffer object. (Note that you must ensure that the lifetime of this stream buffer object exceeds the lifetime of the output stream that uses it.) The open() function returns a pointer to the file buffer object. This pointer is used to construct the output stream object.
//4If no file name is provided, the standard output stream's buffer is used.

As in the original example, out inserts through the standard output stream's buffer, but lacks the special properties of a standard stream.

Here is an alternative solution that uses file descriptors, a non-standard feature of Rogue Wave's implementation of the standard iostreams:

int main(int argc, char argv[])
{
  ofstream out;
  if (argc > 1)     out.open(argv[1]);                        //1
  else              out.rdbuf()->open(1);                     //2
  out << "Hello world!" << endl;
}
//1If the program is provided with a file name, the file is opened and connected to the file buffer object.
//2Otherwise, the output stream's file buffer is connected to the standard input stream stdout whose file descriptor is 1.

The effect is the same as in the previous solution, because the standard output stream cout is connected to the C standard input file stdout. This is the simplest of all solutions, because it doesn't involve reassigning or sharing stream buffers. The output file stream's buffer is simply connected to the right file. However, this is a non-standard solution, and may decrease portability.

2.9.1.3 Using Pointer or References to Streams

If you do not want to deal with stream buffers at all, you can also use pointers or references to streams instead. Here is an example:

int main(int argc, char argv[])
{
  ostream* fp;                                                //1
  if (argc > 1)
      fp = new ofstream(argv[1]);                             //2
  else
     fp = &cout                                               //3

  // output to *fp, e.g. 
  *fp << "Hello world!" << endl;                              //4
  if(fp!=&cout) delete fp;
}
//1A pointer to an ostream is used. (Note that it cannot be a pointer to an ofstream, because the standard output stream cout is not a file stream, but a plain stream of type ostream.)
//2A file stream for the named output file is created on the heap and assigned to the pointer, in case a file name is provided.
//3Otherwise, a pointer to cout is used.
//4Output is written through the pointer to either cout or the named output file.

An alternative approach could use a reference instead of a pointer:

int main(int argc, char argv[])
{
  ostream& fr = (argc > 1) ? *(new ofstream(argv[1])) : cout;
  // output to *fr, e.g. 
  fr << "Hello world!" << endl;
  if (&fr!=&cout) delete(&fr);
}

Working with pointers and references has a drawback: you have to create an output file stream object on the heap and, in principle, you have to worry about deleting the object again, which might lead you into other dire straits.

In summary, creating a copy of a stream is not trivial and should only be done if you really need a copy of a stream object. In many cases, it is more appropriate to use references or pointers to stream objects instead, or to share a stream buffer between two streams.


Keep in mind: Never create a copy of a stream object when a reference or a pointer to the stream object would suffice, or when a shared stream buffer would solve the problem.


2.9.2 Sharing a Stream Buffer Among Streams

Despite the previous caveats, there are situations where sharing a stream buffer among streams is useful and intended. Let us focus on these in this section.

2.9.2.1 Several Format Settings for the Same Stream

Imagine you need different formatting for different kinds of output to the same stream. Instead of switching the format settings between the different kinds of output, you can arrange for two separate streams to share a stream buffer. The streams would have different format settings, but write output to the same stream buffer. Here is an example:

ofstream file1("/tmp/x");
ostream file2(file1.rdbuf());                                 \\1

file1.setf(ios_base::fixed, ios_base::floatfield);            \\2
file1.precision(5);
file2.setf(ios_base::scientific, ios_base::floatfield);
file2.precision(3);

file1 << setw(10) << 47.11 << '\n';                           \\3
file2 << setw(10) << 47.11 << '\n';                           \\4
//1The stream buffer of file1 is replaced by the stream buffer of file2. Afterwards, both streams share the buffer.
//2Create different format settings for both files.
//3The output here will be: 47.11000
//4The output here will be: 4.711e+01

Note that file2 in the example above has to be an output stream rather than an output file stream. This is because file streams do not allow you to switch the file stream buffer.

2.9.2.2 Several Locales for the Same Stream

Similarly, you can use separate streams that share a stream buffer in order to avoid locale switches. This is useful when you have to insert multilingual text into the same stream. Here is an example:

  ostringstream file1;
  ostream file2(file1.rdbuf());

  file1.imbue(locale("De_DE"));
  file2.imbue(locale("En_US"));

  file1 << 47.11 << '\t';
  file2 << 47.11 << '\n';

  cout << file1.str() << endl;                                \\1
//1The output will be: 47,11 47.11

Again, there is a little snag. In Figure 16, note that a stream buffer has a locale object of its own, in addition to the stream's locale object.

Figure 16. Locale objects and shared stream buffers


Section 2.2.2.1 explained the role of those two locale objects. To recap, the stream delegates the handling of numeric entities to its locale's numeric facets. The stream buffer uses its locale's code conversion facet for character-wise transformation between the buffer content and characters transported to and from the external device.

Usually the stream's locale and the stream buffer's locale are identical. However, when you share a stream buffer between two streams with different locales, you must decide which locale the stream buffer will use.

You can set the stream buffer's locale by calling the pubimbue() function as follows:

..file1.imbue(locale("De_DE"));
  file2.imbue(locale("En_US"));
  file1.rdbuf()->pubimbue(locale("De_DE"));

2.9.2.3 Input and Output to the Same Stream

You can also use a shared stream buffer in order to have read and write access to a stream:

filebuf fbuf;                                                 //1
  fbuf.open("/tmp/inout",ios_base::in|ios_base::out);         //2
  istream in(&fbuf);                                          //3
  ostream out(&fbuf);                                         //4
 
  cout << in.rdbuf();                                         //5
  out << "..." << '\n' ;                                      //6
//1Create a file buffer, and
//2Connect it to a file. Note that you have to open the file in input and output mode if you want to read and write to it.
//3Create an input stream that works with the file buffer fbuf.
//4Create an output stream that also uses the file buffer fbuf.
//5Read the entire content of the file and insert it into the standard output stream. Afterwards the file position is at the end of the file.

The most efficient way to read a file's entire content is through the rdbuf() function, which returns a pointer to the underlying stream buffer object. There is an inserter available that takes a stream buffer pointer, so you can insert the buffer's content into another stream.
//6Write output to the file. As the current file position is the end of the file, all output will be inserted at the end.

Naturally, it is easier and less error-prone to use bidirectional streams when you have to read and write to a file. The bidirectional equivalent to the example above would be:

fstream of("/tmp/inout");
  cout << of.rdbuf();
  of << "..." << '\n' ; 

Notice that there is a difference between the solutions that you can see by comparing Figure 17 and Figure 18. An input and an output stream that share a stream buffer, as shown in Figure 17, can still have separate format settings, different locales, different exception masks, and so on.

Figure 17. An input and an output stream sharing a stream buffer


In contrast, the bidirectional stream shown in Figure 18 can have only one format setting, one locale, and so on:

Figure 18. A bidirectional stream


It seems clear that you cannot have different settings for input and output operations when you use a bidirectional stream. Still, it is advisable to use bidirectional file or string streams if you need to read and write to a file or string, instead of creating an input and an output stream that share a stream buffer. The bidirectional stream is easier to declare, and you do not have to worry about the stream buffer object's lifetime.


Please note: It's better to use one bidirectional file or string stream for reading and writing to a file or string, rather than two streams that share a stream buffer.


2.9.3 Copies of the Stream Buffer

The previous section showed how you can read the content of a file in its entirety by using the rdbuf() function. Let us now explore a variation of that example. Imagine another file containing some sort of header information that needs to be analyzed before we start appending the file. Instead of writing the current file content to the standard output stream, we want to process the content before we start adding to it. The easiest way to put the entire file content into a memory location for further processing is by creating a string stream and inserting the file's stream buffer into the string stream:

fstream fil("/tmp/inout");
  stringstream header_stream;                                 //1
  header_stream << fil.rdbuf();                               //2

  // process the header, e.g.
  string word;
  header_stream >> word;                                      //3
//1The easiest way to put the entire file content into a memory location for further processing is by creating a string stream, and
//2Inserting the file's stream buffer into the string stream.
//3We now have the usual facilities of an input stream for reading and analyzing the header information; i.e., operator>>(), read(), get(), and so on.

In cases where this procedure is insufficient, you should create a string that contains the header information and process the header by means of the string operations find(), compare(), etc.

fstream fil("/tmp/inout");
  header_stream << fil.rdbuf();
  string header_string = header_stream.str();

  // process the header, e.g.
  string::size_type pos = header_string.rfind('.');

If the header contains binary data instead of text, even a string will probably not suffice. Here you would want to see the header as a plain byte sequence, i.e., an ordinary char* buffer. But note that a code conversion might already have been performed, depending on the locale attached to the file stream. In cases where you want to process binary data, you have to make sure that the attached locale has a non-converting code conversion facet:

fstream fil("/tmp/inout");
  header_stream << fil.rdbuf();
  string header_string = header_stream.str();
  const char* header_char_ptr = header_string.data();
  
  // process the header, e.g.
  int idx;
  memcpy((char*) &idx,header_char_ptr,sizeof(int));

A note on efficiency: If the header information is extensive, you will have to consider the number of copy operations performed in the previous example. Figure 19 shows how these copies are made:

Figure 19: Copies of the file content


The content of the file is copied into the string stream's buffer when the pointer obtained through rdbuf() is inserted to the string stream. A second copy is created when the string stream's function str() is called. The call to the string's function data() does not create yet another copy, but returns a pointer to the string's internal data.


©Copyright 1996, Rogue Wave Software, Inc.


Powered by Plone This site conforms to the following standards: