Chapter 5: The IO-stream Library

Don't hesitate to send in feedback: send an e-mail if you like the C++ Annotations; if you think that important material was omitted; if you find errors or typos in the text or the code examples; or if you just feel like e-mailing. Send your e-mail to Frank B. Brokken.

Please state the document version you're referring to, as found in the title (in this document: 7.0.0) and please state chapter and paragraph name or number you're referring to.

All received mail is processed conscientiously, and received suggestions for improvements will usually have been processed by the time a new version of the Annotations is released. Except for the incidental case I will normally not acknowledge the receipt of suggestions for improvements. Please don't interpret this as me not appreciating your efforts.

As an extension to the standard stream ( FILE) approach, well known from the C programming language, C++ offers an input/output ( I/O) library based on class concepts.

Earlier (in chapter 3) we've already seen examples of the use of the C++ I/O library, especially the use of the insertion operator (<<) and the extraction operator (>>). In this chapter we'll cover the library in more detail.

The discussion of input and output facilities provided by the C++ programming language heavily uses the class concept, and the notion of member functions. Although the construction of classes will be covered in the upcoming chapter 6, and inheritance will formally be introduced in chapter 13, we think it is well possible to introduce input and output (I/O) facilities long before the technical background of these topics is actually covered.

Most C++ I/O classes have names starting with basic_ (like basic_ios). However, these basic_ names are not regularly found in C++ programs, as most classes are also defined using typedef definitions like:

        typedef basic_ios<char>       ios;
Since C++ defines both the char and wchar_t types, I/O facilities were developed using the template mechanism. As will be further elaborated in chapter 18, this way it was possible to construct generic software, which could thereupon be used for both the char and wchar_t types. So, analogously to the above typedef there exists a
        typedef basic_ios<wchar_t>    wios;
This type definition can be used for the wchar_t type. Because of the existence of these type definitions, the basic_ prefix can be omitted from the Annotations without loss of continuity. In the Annotations the emphasis is primarily on the standard 8-bits char type.

As a side effect to this implementation it must be stressed that it is not anymore correct to declare iostream objects using standard forward declarations, like:

    class ostream;          // now erroneous
Instead, sources that must declare iostream classes must
    #include <iosfwd>       // correct way to declare iostream classes

Using the C++ I/O library offers the additional advantage of type safety. Objects (or plain values) are inserted into streams. Compare this to the situation commonly encountered in C where the fprintf() function is used to indicate by a format string what kind of value to expect where. Compared to this latter situation C++'s iostream approach immediately uses the objects where their values should appear, as in

    cout << "There were " << nMaidens << " virgins present\n";
The compiler notices the type of the nMaidens variable, inserting its proper value at the appropriate place in the sentence inserted into the cout iostream.

Compare this to the situation encountered in C. Although C compilers are getting smarter and smarter over the years, and although a well-designed C compiler may warn you for a mismatch between a format specifier and the type of a variable encountered in the corresponding position of the argument list of a printf() statement, it can't do much more than warn you. The type safety seen in C++ prevents you from making type mismatches, as there are no types to match.

Apart from this, iostreams offer more or less the same set of possibilities as the standard FILE-based I/O used in C: files can be opened, closed, positioned, read, written, etc.. In C++ the basic FILE structure, as used in C, is still available. C++ adds I/O based on classes to FILE-based I/O, resulting in type safety, extensibility, and a clean design. In the ANSI/ISO standard the intent was to construct architecture independent I/O. Previous implementations of the iostreams library did not always comply with the standard, resulting in many extensions to the standard. Software developed earlier may have to be partially rewritten with respect to I/O. This is tough for those who are now forced to modify existing software, but every feature and extension that was available in previous implementations can be reconstructed easily using the ANSI/ISO standard conforming I/O library. Not all of these reimplementations can be covered in this chapter, as most use inheritance and polymorphism, topics that will be covered in chapters 13 and 14, respectively. Selected reimplementations will be provided in chapter 21, and below references to particular sections in that chapter will be given where appropriate.

Figure 3 is shown here.
Figure 3: Central I/O Classes


This chapter is organized as follows (see also Figure 3):

In the iostream library the stream objects have a limited role: they form the interface between, on the one hand, the objects to be input or output and, on the other hand, the streambuf, which is responsible for the actual input and output to the device for which the streambuf object was created in the first place. This approach allows us to construct a new kind of streambuf for a new kind of device, and use that streambuf in combination with the `good old' istream- or ostream-class facilities. It is important to understand the distinction between the formatting roles of the iostream objects and the buffering interface to an external device as implemented in a streambuf. Interfacing to new devices (like sockets or file descriptors) requires us to construct a new kind of streambuf, not a new kind of istream or ostream object. A wrapper class may be constructed around the istream or ostream classes, though, to ease the access to a special device. This is how the stringstream classes were constructed.

5.1: Special header files

Several header files are defined for the iostream library. Depending on the situation at hand, the following header files should be used:

5.2: The foundation: the class `ios_base'

The class ios_base forms the foundation of all I/O operations, and defines, among other things, the facilities for inspecting the state of I/O streams and most output formatting facilities. Every stream class of the I/O library is, via the class ios, derived from this class, and inherits its capabilities.

The discussion of the class ios_base precedes the introduction of members that can be used for actual reading from and writing to streams. But as the ios_base class is the foundation on which all I/O in C++ was built, we introduce it as the first class of the C++ I/O library.

Note, however, that as in C, I/O in C++ is not part of the language (although it is part of the ANSI/ISO standard on C++): although it is technically possible to ignore all predefined I/O facilities, nobody actually does so, and the I/O library represents therefore a de facto I/O standard in C++. Also note that, as mentioned before, the iostream classes do not do input and output themselves, but delegate this to an auxiliary class: the class streambuf or its derivatives.

For the sake of completeness it is noted that it is not possible to construct an ios_base object directly. As covered by chapter 13, classes that are derived from ios_base (like ios) may construct ios_base objects using the ios_base::ios_base() constructor.

The next class in the iostream hierarchy (see figure 3) is the class ios. Since the stream classes inherit from the class ios, and thus also from ios_base, in practice the distinction between ios_base and ios is hardly important. Therefore, facilities actually provided by ios_base will be discussed as facilities provided by ios. The reader who is interested in the true class in which a particular facility is defined should consult the relevant header files (e.g., ios_base.h and basic_ios.h).

5.3: Interfacing `streambuf' objects: the class `ios'

The ios class was derived directly from ios_base, and it defines de facto the foundation for all stream classes of the C++ I/O library.

Although it is possible to construct an ios object directly, this is hardly ever done. The purpose of the class ios is to provide the facilities of the class basic_ios, and to add several new facilites, all related to managing the streambuf object which is managed by objects of the class ios.

All other stream classes are either directly or indirectly derived from ios. This implies, as explained in chapter 13, that all facilities offered by the classes ios and ios_base are also available in other stream classes. Before discussing these additional stream classes, the facilities offered by the class ios (and by implication: by ios_base) are now introduced.

The class ios offers several member functions, most of which are related to formatting. Other frequently used member functions are:

5.3.1: Condition states

Operations on streams may succeed and they may fail for several reasons. Whenever an operation fails, further read and write operations on the stream are suspended. It is possible to inspect (and possibly: clear) the condition state of streams, so that a program can repair the problem, instead of having to abort.

Conditions are represented by the following condition flags:

Several condition member functions are available to manipulate or determine the states of ios objects. Originally they returned int values, but their current return type is bool:

The following members are available to manage error states:

C++ supports an exception mechanism for handling exceptional situations. According to the ANSI/ISO standard, exceptions can be used with stream objects. Exceptions are covered in chapter 8. Using exceptions with stream objects is covered in section 8.7.

5.3.2: Formatting output and input

The way information is written to streams (or, occasionally, read from streams) may be controlled by formatting flags.

Formatting is used when it is necessary to control the width of an output field or an input buffer and if formatting is used to determine the form (e.g., the radix) in which a value is displayed. Most formatting belongs to the realm of the ios class, although most formatting is actually used with output streams, like the upcoming ostream class. Since the formatting is controlled by flags, defined in the ios class, it was considered best to discuss formatting with the ios class itself, rather than with a selected derived class, where the choice of the derived class would always be somewhat arbitrarily.

Formatting is controlled by a set of formatting flags. These flags can basically be altered in two ways: using specialized member functions, discussed in section 5.3.2.2 or using manipulators, which are directly inserted into streams. Manipulators are not applied directly to the ios class, as they require the use of the insertion operator. Consequently they are discussed later (in section 5.6).

5.3.2.1: Formatting flags Most formatting flags are related to outputting information. Information can be written to output streams in basically two ways: binary output will write information directly to the output stream, without conversion to some human-readable format. E.g., an int value is written as a set of four bytes. Alternatively, formatted output will convert the values that are stored in bytes in the computer's memory to ASCII-characters, in order to create a human-readable form.

Formatting flags can be used to define the way this conversion takes place, to control, e.g., the number of characters that are written to the output stream.

The following formatting flags are available (see also sections 5.3.2.2 and 5.6):

5.3.2.2: Format modifying member functions Several member functions are available for I/O formatting. Often, corresponding manipulators exist, which may directly be inserted into or extracted from streams using insertion or extraction operators. See section 5.6 for a discussion of the available manipulators. They are:

5.4: Output

In C++ output is primarily based on the ostream class. The ostream class defines the basic operators and members for inserting information into streams: the insertion operator (<<), and special members like ostream::write() for writing unformatted information from streams.

From the class ostream several other classes are derived, all having the functionality of the ostream class, and adding their own specialties. In the next sections on `output' we will introduce:

5.4.1: Basic output: the class `ostream'

The class ostream is the class defining basic output facilities. The cout, clog and cerr objects are all ostream objects. Note that all facilities defined in the ios class, as far as output is concerned, is available in the ostream class as well, due to the inheritance mechanism (discussed in chapter 13).

We can construct ostream objects using the following ostream constructor:

In order to use the ostream class in C++ sources, the #include <ostream> preprocessor directive must be given. To use the predefined ostream objects, the #include <iostream> preprocessor directive must be given.

5.4.1.1: Writing to `ostream' objects The class ostream supports both formatted and binary output.

The insertion operator (<<) may be used to insert values in a type safe way into ostream objects. This is called formatted output, as binary values which are stored in the computer's memory are converted to human-readable ASCII characters according to certain formatting rules.

Note that the insertion operator points to the ostream object wherein the information must be inserted. The normal associativity of << remains unaltered, so when a statement like

    cout << "hello " << "world";
is encountered, the leftmost two operands are evaluated first (cout << "hello "), and an ostream & object, which is actually the same cout object, is returned. Now, the statement is reduced to
    cout << "world";
and the second string is inserted into cout.

The << operator has a lot of (overloaded) variants, so many types of variables can be inserted into ostream objects. There is an overloaded <<-operator expecting an int, a double, a pointer, etc. etc.. For every part of the information that is inserted into the stream the operator returns the ostream object into which the information so far was inserted, and the next part of the information to be inserted is processed.

Streams do not have facilities for formatted output like C's form() and vform() functions. Although it is not difficult to realize these facilities in the world of streams, form()-like functionality is hardly ever required in C++ programs. Furthermore, as it is potentially type-unsafe, it might be better to avoid this functionality completely.

When binary files must be written, normally no text-formatting is used or required: an int value should be written as a series of unaltered bytes, not as a series of ASCII numeric characters 0 to 9. The following member functions of ostream objects may be used to write `binary files':

5.4.1.2: `ostream' positioning Although not every ostream object supports repositioning, they usually do. This means that it is possible to rewrite a section of the stream which was written earlier. Repositioning is frequently used in database applications where it must be possible to access the information in the database randomly.

The following members are available:

5.4.1.3: `ostream' flushing Unless the ios::unitbuf flag has been set, information written to an ostream object is not immediately written to the physical stream. Rather, an internal buffer is filled up during the write-operations, and when full it is flushed.

The internal buffer can be flushed under program control:

5.4.2: Output to files: the class `ofstream'

The ofstream class is derived from the ostream class: it has the same capabilities as the ostream class, but can be used to access files or create files for writing.

In order to use the ofstream class in C++ sources, the preprocessor directive #include <fstream> must be given. After including fstream cin, cout etc. are not automatically declared. If these latter objects are needed too, then iostream should be included.

The following constructors are available for ofstream objects:

Note that it is not possible to open a ofstream using a file descriptor. The reason for this is (apparently) that file descriptors are not universally available over different operating systems. Fortunately, file descriptors can be used (indirectly) with a streambuf object (and in some implementations: with a filebuf object, which is also a streambuf). Streambuf objects are discussed in section 5.7, filebuf objects are discussed in section 5.7.2.

Instead of directly associating an ofstream object with a file, the object can be constructed first, and opened later.

A subtlety is the following: Assume a stream is constructed, but it is not actually attached to a file. E.g., the statement ofstream ostr was executed. When we now check its status through good(), a non-zero (i.e., ok) value will be returned. The `good' status here indicates that the stream object has been properly constructed. It doesn't mean the file is also open. To test whether a stream is actually open, inspect ofstream::is_open(): If true, the stream is open. See the following example:
    #include <fstream>
    #include <iostream>

    using namespace std;

    int main()
    {
        ofstream of;

        cout << "of's open state: " << boolalpha << of.is_open() << endl;

        of.open("/dev/null");       // on Unix systems

        cout << "of's open state: " << of.is_open() << endl;
    }
    /*
        Generated output:
    of's open state: false
    of's open state: true
    */

5.4.2.1: Modes for opening stream objects The following file modes or file flags are defined for constructing or opening ofstream (or istream, see section 5.5.2) objects. The values are of type ios::openmode:

The following combinations of file flags have special meanings:
    out | app:          The file is created if non-existing,
                        information is always added to the end of the
                        stream;
    out | trunc:        The file is (re)created empty to be written;
    in | out:           The stream may be read and written. However, the
                        file must exist.
    in | out | trunc:   The stream may be read and written. It is
                        (re)created empty first.

5.4.3: Output to memory: the class `ostringstream'

In order to write information to memory, using the stream facilities, ostringstream objects can be used. These objects are derived from ostream objects. The following constructors and members are available: Before the stringstream class was available the class ostrstream was commonly used for doing output to memory. This latter class suffered from the fact that, once its contents were retrieved using its str() member function, these contents were `frozen', meaning that its dynamically allocated memory was not released when the object went out of scope. Although this situation could be prevented (using the ostrstream member call freeze(0)), this implementation could easily lead to memory leaks. The stringstream class does not suffer from these risks. Therefore, the use of the class ostrstream is now deprecated in favor of ostringstream.

The following example illustrates the use of the ostringstream class: several values are inserted into the object. Then, the stored text is stored in a string, whose length and contents are thereupon printed. Such ostringstream objects are most often used for doing `type to string' conversions, like converting int to string. Formatting commands can be used with stringstreams as well, as they are available in ostream objects. Here is an example showing the use of an ostringstream object:

    #include <iostream>
    #include <string>
    #include <sstream>
    #include <fstream>

    using namespace std;

    int main()
    {
        ostringstream ostr("hello ", ios::ate);

        cout << ostr.str() << endl;

        ostr.setf(ios::showbase);
        ostr.setf(ios::hex, ios::basefield);
        ostr << 12345;

        cout << ostr.str() << endl;

        ostr << " -- ";
        ostr.unsetf(ios::hex);
        ostr << 12;

        cout << ostr.str() << endl;
    }
    /*
        Output from this program:
    hello
    hello 0x3039
    hello 0x3039 -- 12
    */

5.5: Input

In C++ input is primarily based on the istream class. The istream class defines the basic operators and members for extracting information from streams: the extraction operator (>>), and special members like istream::read() for reading unformatted information from streams.

From the class istream several other classes are derived, all having the functionality of the istream class, and adding their own specialties. In the next sections we will introduce:

5.5.1: Basic input: the class `istream'

The class istream is the I/O class defining basic input facilities. The cin object is an istream object that is declared when sources contain the preprocessor directive #include <iostream>. Note that all facilities defined in the ios class are, as far as input is concerned, available in the istream class as well due to the inheritance mechanism (discussed in chapter 13).

Istream objects can be constructed using the following istream constructor:

In order to use the istream class in C++ sources, the #include <istream> preprocessor directive must be given. To use the predefined istream object cin, the #include <iostream> preprocessor directive must be given.

5.5.1.1: Reading from `istream' objects The class istream supports both formatted and unformatted binary input. The extraction operator ( operator>>()) may be used to extract values in a type safe way from istream objects. This is called formatted input, whereby human-readable ASCII characters are converted, according to certain formatting rules, to binary values which are stored in the computer's memory.

Note that the extraction operator points to the objects or variables which must receive new values. The normal associativity of >> remains unaltered, so when a statement like

    cin >> x >> y;
is encountered, the leftmost two operands are evaluated first (cin >> x), and an istream & object, which is actually the same cin object, is returned. Now, the statement is reduced to
    cin >> y
and the y variable is extracted from cin.

The >> operator has a lot of (overloaded) variants, so many types of variables can be extracted from istream objects. There is an overloaded >> available for the extraction of an int, of a double, of a string, of an array of characters, possibly to a pointer, etc. etc.. String or character array extraction will (by default) skip all white space characters, and will then extract all consecutive non-white space characters. After processing an extraction operator, the istream object into which the information so far was inserted is returned, which will thereupon be used as the lvalue for the remaining part of the statement.

Streams do not have facilities for formatted input (like C's scanf() and vscanf() functions). Although it is not difficult to make these facilities available in the world of streams, scanf()-like functionality is hardly ever required in C++ programs. Furthermore, as it is potentially type-unsafe, it might be better to avoid this functionality completely.

When binary files must be read, the information should normally not be formatted: an int value should be read as a series of unaltered bytes, not as a series of ASCII numeric characters 0 to 9. The following member functions for reading information from istream objects are available:

5.5.1.2: `istream' positioning Although not every istream object supports repositioning, some do. This means that it is possible to read the same section of a stream repeatedly. Repositioning is frequently used in database applications where it must be possible to access the information in the database randomly.

The following members are available:

5.5.2: Input from streams: the class `ifstream'

The class ifstream is derived from the class istream: it has the same capabilities as the istream class, but can be used to access files for reading. Such files must exist.

In order to use the ifstream class in C++ sources, the preprocessor directive #include <fstream> must be given.

The following constructors are available for ifstream objects:

Instead of directly associating an ifstream object with a file, the object can be constructed first, and opened later. A subtlety is the following: Assume a stream is constructed, but it is not actually attached to a file. E.g., the statement ifstream ostr was executed. When we now check its status through good(), a non-zero (i.e., ok) value will be returned. The `good' status here indicates that the stream object has been properly constructed. It doesn't mean the file is also open. To test whether a stream is actually open, inspect ifstream::is_open(): If true, the stream is open. See also the example in section 5.4.2.

To illustrate reading from a binary file (see also section 5.5.1.1), a double value is read in binary form from a file in the next example:

    #include <fstream>
    using namespace std;

    int main(int argc, char **argv)
    {
        ifstream f(argv[1]);
        double   d;

        // reads double in binary form.
        f.read(reinterpret_cast<char *>(&d), sizeof(double));
    }

5.5.3: Input from memory: the class `istringstream'

In order to read information from memory, using the stream facilities, istringstream objects can be used. These objects are derived from istream objects. The following constructors and members are available:

The istringstream object is commonly used for converting ASCII text to its binary equivalent, like the C function atoi(). The following example illustrates the use of the istringstream class, note especially the use of the member seekg():

    #include <iostream>
    #include <string>
    #include <sstream>

    using namespace std;

    int main()
    {
        istringstream istr("123 345");  // store some text.
        int x;

        istr.seekg(2);              // skip "12"
        istr >> x;                  // extract int
        cout << x << endl;          // write it out
        istr.seekg(0);              // retry from the beginning
        istr >> x;                  // extract int
        cout << x << endl;          // write it out
        istr.str("666");            // store another text
        istr >> x;                  // extract it
        cout << x << endl;          // write it out
    }
    /*
        output of this program:
    3
    123
    666
    */

5.6: Manipulators

Ios objects define a set of format flags that are used for determining the way values are inserted (see section 5.3.2.1). The format flags can be controlled by member functions (see section 5.3.2.2), but also by manipulators. Manipulators are inserted into output streams or extracted from input streams, instead of being activated through the member selection operator (`.').

Manipulators are functions. New manipulators can be constructed as well. The construction of manipulators is covered in section 9.10.1. In this section the manipulators that are available in the C++ I/O library are discussed. Most manipulators affect format flags. See section 5.3.2.1 for details about these flags. Most manipulators are parameterless. Sources in which manipulators expecting arguments are used, must do:

    #include <iomanip>

5.7: The `streambuf' class

The class streambuf defines the input and output character sequences that are processed by streams. Like an ios object, a streambuf object is not directly constructed, but is implied by objects of other classes that are specializations of the class streambuf.

The class plays an important role in realizing possibilities that were available as extensions to the pre- ANSI/ISO standard implementations of C++. Although the class cannot be used directly, its members are introduced here, as the current chapter is the most logical place to introduce the class streambuf. However, this section of the current chapter assumes a basic familiarity with the concept of polymorphism, a topic discussed in detail in chapter 14. Readers not yet familiar with the concept of polymorphism may, for the time being, skip this section without loss of continuity.

The primary reason for existence of the class streambuf, however, is to decouple the stream classes from the devices they operate upon. The rationale here is to use an extra software layer between on the one hand the classes allowing us to communicate with the device and the communication between the software and the devices themselves. This implements a chain of command which is seen regularly in software design: The chain of command is considered a generic pattern for the construction of reusable software, encountered also in, e.g., the TCP/IP stack. A streambuf can be considered yet another example of the chain of command pattern: here the program talks to stream objects, which in turn forward their requests to streambuf objects, which in turn communicate with the devices. Thus, as we will see shortly, we are now able to do in user-software what had to be done via (expensive) system calls before.

The class streambuf has no public constructor, but does make available several public member functions. In addition to these public member functions, several member functions are available to specializing classes only. These protected members are listed in this section for further reference. In section 5.7.2 below, a particular specialization of the class streambuf is introduced. Note that all public members of streambuf discussed here are also available in filebuf.

In section 14.6 the process of constructing specializations of the class streambuf is discussed, and in chapter 21 several other implications of using streambuf objects are mentioned. In the current chapter examples of copying streams, of redirecting streams and and of reading and writing to streams using the streambuf members of stream objects are presented (section 5.8).

With the class streambuf the following public member functions are available. The type streamsize that is used below may, for all practical purposes, be considered an unsigned int.

Public members for input operations:

Public members for output operations:

Public members for miscellaneous operations:

5.7.1: Protected `streambuf' members

The protected members of the class streambuf are normally not accessible. However, they are accessible in specializing classes which are derived from streambuf. They are important for understanding and using the class streambuf. Usually there are both protected data members and protected member functions defined in the class streambuf. Since using data members immediately violates the principle of encapsulation, these members are not mentioned here. As the functionality of streambuf, made available via its member functions, is quite extensive, directly using its data members is probably hardly ever necessary. This section not even lists all protected member functions of the class streambuf. Only those member functions are mentioned that are useful in constructing specializations. The class streambuf maintains an input- and/or and output buffer, for which begin-, actual- and end-pointers have been defined, as depicted in figure 4. In upcoming sections we will refer to this figure repeatedly.

Figure 4 is shown here.
Figure 4: Input- and output buffer pointers of the class `streambuf'


Protected constructor:

Several protected member functions are related to input operations. The member functions marked as virtual may be redefined in classes derived from streambuf. In those cases, the redefined function will be called by i/ostream objects that received the addresses of such derived class objects. See chapter 14 for details about virtual member functions. Here are the protected members:

Here are the protected member functions related to output operations. Similarly to the functions related to input operations, some of the following functions are virtual: they may be redefined in derived classes:

Protected member functions related to buffer management and positioning:

Morale: when specializations of the class streambuf are designed, the very least thing to do is to redefine underflow() for specializations aimed at reading information from devices, and to redefine overflow() for specializations aimed at writing information to devices. Several examples of specializations of the class streambuf will be given in the C++ Annotations (e.g., in chapter 21).

Objects of the class fstream use a combined input/output buffer. This results from the fact that istream and ostream, are virtually derived from ios, which contains the streambuf. As explained in section 14.4.2, this implies that classes derived from both istream and ostream share their streambuf pointer. In order to construct a class supporting both input and output on separate buffers, the streambuf itself may define internally two buffers. When seekoff() is called for reading, its mode parameter is set to ios::in, otherwise to ios::out. This way, the streambuf specializaiton knows whether it should access the read buffer or the write buffer. Of course, underflow() and overflow() themselves already know on which buffer they should operate.

5.7.2: The class `filebuf'

The class filebuf is a specialization of streambuf used by the file stream classes. Apart from the (public) members that are available through the class streambuf, it defines the following extra (public) members: Before filebuf objects can be defined the following preprocessor directive must have been specified:
    #include <fstream>

5.8: Advanced topics

5.8.1: Copying streams

Usually, files are copied either by reading a source file character by character or line by line. The basic mold for processing files is as follows: It is important to note that the reading must precede the testing, as it is only possible to know after the actual attempt to read from a file whether the reading succeeded or not. Of course, variations are possible: getline(istream &, string &) (see section 5.5.1.1) returns an istream & itself, so here reading and testing may be realized in one expression. Nevertheless, the above mold represents the general case. So, the following program could be used to copy cin to cout:
#include <iostream>

using namespace::std;

int main()
{
    while (true)
    {
        char c;

        cin.get(c);
        if (cin.fail())
            break;
        cout << c;
    }
    return 0;
}

By combining the get() with the if-statement a construction comparable to getline() could be used:

    if (!cin.get(c))
        break;
Note, however, that this would still follow the basic rule: ` read first, test later'.

This simple copying of a file, however, isn't required very often. More often, a situation is encountered where a file is processed up to a certain point, whereafter the remainder of the file can be copied unaltered. The following program illustrates this situation: the ignore() call is used to skip the first line (for the sake of the example it is assumed that the first line is at most 80 characters long), the second statement uses a special overloaded version of the <<-operator, in which a streambuf pointer is inserted into another stream. As the member rdbuf() returns a streambuf *, it can thereupon be inserted into cout. This immediately copies the remainder of cin to cout:

    #include <iostream>
    using namespace std;

    int main()
    {
        cin.ignore(80, '\n');   // skip the first line
        cout << cin.rdbuf();    // copy the rest by inserting a streambuf *
    }
Note that this method assumes a streambuf object, so it will work for all specializations of streambuf. Consequently, if the class streambuf is specialized for a particular device it can be inserted into any other stream using the above method.

5.8.2: Coupling streams

Ostreams can be coupled to ios objects using the tie() member function. This results in flushing all buffered output of the ostream object (by calling flush()) whenever an input or output operation is performed on the ios object to which the ostream object is tied. By default cout is tied to cin (i.e., cin.tie(cout)): whenever an operation on cin is requested, cout is flushed first. To break the coupling, the member function ios::tie(0) can be called.

Another (frequently useful, but non-default) example of coupling streams is to tie cerr to cout: this way standard output and error messages written to the screen will appear in sync with the time at which they were generated:

    #include <iostream>
    using namespace std;

    int main()
    {
        cout << "first (buffered) line to cout ";
        cerr << "first (unbuffered) line to cerr\n";
        cout << "\n";

        cerr.tie(&cout);

        cout << "second (buffered) line to cout ";
        cerr << "second (unbuffered) line to cerr\n";
        cout << "\n";
    }
    /*
        Generated output:

    first (buffered) line to cout
    first (unbuffered) line to cerr
    second (buffered) line to cout second (unbuffered) line to cerr

    */

An alternative way to couple streams is to make streams use a common streambuf object. This can be realized using the ios::rdbuf(streambuf *) member function. This way two streams can use, e.g. their own formatting, one stream can be used for input, the other for output, and redirection using the iostream library rather than operating system calls can be realized. See the next sections for examples.

5.8.3: Redirecting streams

By using the ios::rdbuf() member streams can share their streambuf objects. This means that the information that is written to a stream will actually be written to another stream, a phenomenon normally called redirection. Redirection is normally realized at the level of the operating system, and in some situations that is still necessary (see section 21.3.1).

A standard situation where redirection is wanted is to write error messages to file rather than to standard error, usually indicated by its file descriptor number 2. In the Unix operating system using the bash shell, this can be realized as follows:

    program 2>/tmp/error.log
With this command any error messages written by program will be saved on the file /tmp/error.log, rather than being written to the screen.

Here is how this can be realized using streambuf objects. Assume program now expects an optional argument defining the name of the file to write the error messages to; so program is now called as:

    program /tmp/error.log
Here is the example realizing redirection. It is annotated below.
    #include <iostream>
    #include <streambuf>
    #include <fstream>

    using namespace std;

    int main(int argc, char **argv)
    {
        ofstream errlog;                                // 1
        streambuf *cerr_buffer = 0;                     // 2

        if (argc == 2)
        {
            errlog.open(argv[1]);                       // 3
            cerr_buffer = cerr.rdbuf(errlog.rdbuf());   // 4
        }
        else
        {
            cerr << "Missing log filename\n";
            return 1;
        }

        cerr << "Several messages to stderr, msg 1\n";
        cerr << "Several messages to stderr, msg 2\n";

        cout << "Now inspect the contents of " <<
                argv[1] << "... [Enter] ";
        cin.get();                                      // 5

        cerr << "Several messages to stderr, msg 3\n";

        cerr.rdbuf(cerr_buffer);                        // 6
        cerr << "Done\n";                               // 7
    }
    /*
        Generated output on file argv[1]

        at cin.get():

    Several messages to stderr, msg 1
    Several messages to stderr, msg 2

        at the end of the program:

    Several messages to stderr, msg 1
    Several messages to stderr, msg 2
    Several messages to stderr, msg 3
    */

5.8.4: Reading AND Writing streams

In order to both read and write to a stream an fstream object must be created. As with ifstream and ofstream objects, its constructor receives the name of the file to be opened:
        fstream inout("iofile", ios::in | ios::out);
Note the use of the ios constants ios::in and ios::out, indicating that the file must be opened for both reading and writing. Multiple mode indicators may be used, concatenated by the binary or operator '|'. Alternatively, instead of ios::out, ios::app could have been used, in which case writing will always be done at the end of the file.

Somehow reading and writing to a file is a bit awkward: what to do when the file may or may not exist yet, but if it already exists it should not be rewritten? I have been fighting with this problem for some time, and now I use the following approach:

    #include <fstream>
    #include <iostream>
    #include <string>

    using namespace std;

    int main()
    {
        fstream rw("fname", ios::out | ios::in);
        if (!rw)
        {
            rw.clear();
            rw.open("fname", ios::out | ios::trunc | ios::in);
        }
        if (!rw)
        {
            cerr << "Opening `fname' failed miserably" << endl;
            return 1;
        }

        cerr << rw.tellp() << endl;

        rw << "Hello world" << endl;
        rw.seekg(0);

        string s;
        getline(rw, s);

        cout << "Read: " << s << endl;
    }
In the above example, the constructor fails when fname doesn't exist yet. However, in that case the open() member will normally succeed since the file is created due to the ios::trunc flag. If the file already existed, the constructor will succeed. If the ios::ate flag would have been specified as well with rw's initial construction, the first read/write action would by default have take place at EOF. However, ios::ate is not ios::app, so it would then still have been possible to repositioned rw using seekg() or seekp().

Under DOS-like operating systems, which use the multiple character \r\n sentinels to separate lines in text files the flag ios::binary is required for processing binary files to ensure that \r\n combinations are processed as two characters.

With fstream objects, combinations of file flags are used to make sure that a stream is or is not (re)created empty when opened. See section 5.4.2.1 for details.

Once a file has been opened in read and write mode, the << operator can be used to insert information to the file, while the >> operator may be used to extract information from the file. These operations may be performed in random order. The following fragment will read a blank-delimited word from the file, and will then write a string to the file, just beyond the point where the string just read terminated, followed by the reading of yet another string just beyond the location where the string just written ended:

    fstream f("filename", ios::in | ios::out | ios::trunc);
    string  str;

    f >> str;       // read the first word
                    // write a well known text
    f << "hello world";
    f >> str;       // and read again
Since the operators << and >> can apparently be used with fstream objects, you might wonder whether a series of << and >> operators in one statement might be possible. After all, f >> str should produce an fstream &, shouldn't it?

The answer is: it doesn't. The compiler casts the fstream object into an ifstream object in combination with the extraction operator, and into an ofstream object in combination with the insertion operator. Consequently, a statement like

    f >> str << "grandpa" >> str;
results in a compiler error like
    no match for `operator <<(class istream, char[8])'
Since the compiler complains about the istream class, the fstream object is apparently considered an ifstream object in combination with the extraction operator.

Of course, random insertions and extractions are hardly used. Generally, insertions and extractions take place at specific locations in the file. In those cases, the position where the insertion or extraction must take place can be controlled and monitored by the seekg() and tellg() member functions (see sections 5.4.1.2 and 5.5.1.2).

Error conditions (see section 5.3.1) occurring due to, e.g., reading beyond end of file, reaching end of file, or positioning before begin of file, can be cleared using the clear() member function. Following clear() processing may continue. E.g.,

    fstream f("filename", ios::in | ios::out | ios::trunc);
    string  str;

    f.seekg(-10);   // this fails, but...
    f.clear();      // processing f continues

    f >> str;       // read the first word
A common situation in which files are both read and written occurs in data base applications, where files consists of records of fixed size, and where the location and size of pieces of information is well known. For example, the following program may be used to add lines of text to a (possibly existing) file, and to retrieve a certain line, based on its order-numer from the file. Note the use of the binary file index to retrieve the location of the first byte of a line.
    #include <iostream>
    #include <fstream>
    #include <string>
    using namespace std;

    void err(char const *msg)
    {
        cout << msg << endl;
        return;
    }

    void err(char const *msg, long value)
    {
        cout << msg << value << endl;
        return;
    }

    void read(fstream &index, fstream &strings)
    {
        int idx;

        if (!(cin >> idx))                          // read index
            return err("line number expected");

        index.seekg(idx * sizeof(long));            // go to index-offset

        long offset;

        if
        (
            !index.read                             // read the line-offset
            (
                reinterpret_cast<char *>(&offset),
                sizeof(long)
            )
        )
            return err("no offset for line", idx);

        if (!strings.seekg(offset))                 // go to the line's offset
            return err("can't get string offet ", offset);

        string line;

        if (!getline(strings, line))                // read the line
            return err("no line at ", offset);

        cout << "Got line: " << line << endl;       // show the line
    }


    void write(fstream &index, fstream &strings)
    {
        string line;

        if (!getline(cin, line))                  // read the line
            return err("line missing");

        strings.seekp(0, ios::end);               // to strings
        index.seekp(0, ios::end);                 // to index

        long offset = strings.tellp();

        if
        (
            !index.write                          // write the offset to index
            (
                reinterpret_cast<char *>(&offset),
                sizeof(long)
            )
        )
            err("Writing failed to index: ", offset);

        if (!(strings << line << endl))           // write the line itself
            err("Writing to `strings' failed");
                                                  // confirm writing the line
        cout << "Write at offset " << offset << " line: " << line << endl;
    }

    int main()
    {
        fstream index("index", ios::trunc | ios::in | ios::out);
        fstream strings("strings", ios::trunc | ios::in | ios::out);

        cout << "enter `r <number>' to read line <number> or "
                                    "w <line>' to write a line\n"
                "or enter `q' to quit.\n";

        while (true)
        {
            cout << "r <nr>, w <line>, q ? ";       // show prompt

            string cmd;

            cin >> cmd;                             // read cmd

            if (cmd == "q")                         // process the cmd.
                return 0;

            if (cmd == "r")
                read(index, strings);
            else if (cmd == "w")
                write(index, strings);
            else
                cout << "Unknown command: " << cmd << endl;
        }
    }
As another example of reading and writing files, consider the following program, which also serves as an illustration of reading an ASCII-Z delimited string:
    #include <iostream>
    #include <fstream>
    using namespace std;

    int main()
    {                                       // r/w the file
        fstream f("hello", ios::in | ios::out | ios::trunc);

        f.write("hello", 6);                // write 2 ascii-z
        f.write("hello", 6);

        f.seekg(0, ios::beg);               // reset to begin of file

        char buffer[100];                   // or: char *buffer = new char[100]
        char c;
                                            // read the first `hello'
        cout << f.get(buffer, sizeof(buffer), 0).tellg() << endl;;
        f >> c;                             // read the ascii-z delim

                                            // and read the second `hello'
        cout << f.get(buffer + 6, sizeof(buffer) - 6, 0).tellg() << endl;

        buffer[5] = ' ';                    // change asciiz to ' '
        cout << buffer << endl;             // show 2 times `hello'
    }
    /*
        Generated output:
    5
    11
    hello hello
    */

A completely different way to both read and write to streams can be implemented using the streambuf members of stream objects. All considerations mentioned so far remain valid: before a read operation following a write operation seekg() must be used, and before a write operation following a read operation seekp() must be used. When the stream's streambuf objects are used, either an istream is associated with the streambuf object of another ostream object, or vice versa, an ostream object is associated with the streambuf object of another istream object. Here is the same program as before, now using associated streams:

    #include <iostream>
    #include <fstream>
    #include <string>
    using namespace std;

    void err(char const *msg)
    {
        cout << msg << endl;
        return;
    }

    void err(char const *msg, long value)
    {
        cout << msg << value << endl;
        return;
    }

    void read(istream &index, istream &strings)
    {
        int idx;

        if (!(cin >> idx))                          // read index
            return err("line number expected");

        index.seekg(idx * sizeof(long));            // go to index-offset

        long offset;

        if
        (
            !index.read                             // read the line-offset
            (
                reinterpret_cast<char *>(&offset),
                sizeof(long)
            )
        )
            return err("no offset for line", idx);

        if (!strings.seekg(offset))                 // go to the line's offset
            return err("can't get string offet ", offset);

        string line;

        if (!getline(strings, line))                // read the line
            return err("no line at ", offset);

        cout << "Got line: " << line << endl;       // show the line
    }


    void write(ostream &index, ostream &strings)
    {
        string line;

        if (!getline(cin, line))                  // read the line
            return err("line missing");

        strings.seekp(0, ios::end);               // to strings
        index.seekp(0, ios::end);                 // to index

        long offset = strings.tellp();

        if
        (
            !index.write                          // write the offset to index
            (
                reinterpret_cast<char *>(&offset),
                sizeof(long)
            )
        )
            err("Writing failed to index: ", offset);

        if (!(strings << line << endl))           // write the line itself
            err("Writing to `strings' failed");
                                                  // confirm writing the line
        cout << "Write at offset " << offset << " line: " << line << endl;
    }

    int main()
    {
        ifstream index_in("index", ios::trunc | ios::in | ios::out);
        ifstream strings_in("strings", ios::trunc | ios::in | ios::out);
        ostream  index_out(index_in.rdbuf());
        ostream  strings_out(strings_in.rdbuf());

        cout << "enter `r <number>' to read line <number> or "
                                    "w <line>' to write a line\n"
                "or enter `q' to quit.\n";

        while (true)
        {
            cout << "r <nr>, w <line>, q ? ";       // show prompt

            string cmd;

            cin >> cmd;                             // read cmd

            if (cmd == "q")                         // process the cmd.
                return 0;

            if (cmd == "r")
                read(index_in, strings_in);
            else if (cmd == "w")
                write(index_out, strings_out);
            else
                cout << "Unknown command: " << cmd << endl;
        }
    }
Please note: