Rogue Wave banner
Previous fileTop of DocumentContentsNext file

13.2 Copying and Assigning Stream Objects

Stream objects cannot simply be copied and assigned. Let us consider a practical example to see what this 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:

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.17


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 defined18; 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 30 provides a sketch of the data held by a stream object:

Figure 30 -- Data held by a stream object. Some data members are omitted for simplicity.

Diagram showing data held by stream object, which contains the stream buffer that has pointers to the character buffer.

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.

13.2.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 follows:

//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, as in cout.exceptions(fil.exceptions()), but 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 31:

Figure 31 -- Copying a stream's internal data results in a shared buffer

Diagram showing example of copying a stream's internal data results in a shared buffer.

13.2.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:

//1Copy the values of member variables (other than the streambuffer and the iostate) in cout to out.
//2Set state flags for out to the current state of cout.
//3Replace out's streambuffer with cout's streambuffer.

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 Chapter 14 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:

//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 nonstandard feature of Rogue Wave's implementation of the standard iostreams19:

//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 nonstandard solution, and may decrease portability.

13.2.3 Using Pointers 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:

//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:

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.


NOTE: 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.

Previous fileTop of DocumentContentsNext file

©Copyright 1998, Rogue Wave Software, Inc.
Send mail to report errors or comment on the documentation.


OEM Release, June 1998