course of C++ programming language

lecture 12, 13: streams


C++ streams

A stream is a logical device that either produces or consumes information. A stream is linked to a physical device by the I/O system. All streams behave in the same way even though the actual physical devices they are connected to may differ substantially. Because all streams behave the same, the same I/O functions can operate on virtually any type of physical device. For example, you can use the same function that writes to a file to write to the memory or to the screen. The advantage to this approach is that you need learn only one I/O system.

Standard C++ provides support for its I/O system in <iostream>. In this header, a rather complicated set of class hierarchies is defined that supports I/O operations. The I/O classes begin with a system of template classes. A template class defines the form of a class without fully specifying the data upon which it will operate. Once a template class has been defined, specific instances of it can be created. As it relates to the I/O library, Standard C++ creates two specializations of the I/O template classes: one for 8-bit characters (of type char) and another for wide characters (of type wchar_t). We will use only the 8-bit character classes since they are by far the most common. But the same techniques apply to both.

The C++ I/O system is built upon two related but different template class hierarchies. The first is derived from the low-level I/O class called basic_streambuf. This class supplies the basic, low-level input and output operations, and provides the underlying support for the entire C++ I/O system. Unless you are doing advanced I/O programming, you will not need to use basic_streambuf directly. The class hierarchy that you will most commonly be working with is derived from basic_ios. This is a high-level I/O class that provides formatting, error checking, and status information related to stream I/O. A base class for basic_ios is called ios_base, which defines several nontemplate traits used by basic_ios. basic_ios is used as a base for several derived classes, including basic_istream, basic_ostream, and basic_iostream. These classes are used to create streams capable of input, output, and input/output, respectively.

template classcharacter-based classwide-character-based class
basic_streambufstreambufwstreambuf
basic_iosioswios
basic_istreamistreamwistream
basic_ostreamostreamwostream
basic_iostreamiostreamwiostream
basic_fstreamfstreamwfstream
basic_ifstreamifstreamwifstream
basic_ofstreamofstreamwofstream

Predefined streams

When a C++ program begins execution, four built-in streams are automatically opened. Streams cin, cout, and cerr correspond to C's stdin, stdout, and stderr. By default, the standard streams are used to communicate with the console.

streammeaningdefault device
cinstandard inputkeyboard
coutstandard outputscreen
cerrstandard error outputscreen
clogbuffered version of cerrscreen

Standard C++ also defines these four additional streams: win, wout, werr, and wlog. These are wide-character versions of the standard streams. Wide characters are generally 16-bit quantities. Wide characters are used to hold the large character sets associated with some human languages.

Formating using the ios members

Each stream has associated with it a set of format flags that control the way information is formatted. The ios class declares a bitmask enumeration called fmtflags in which the following values are defined. Technically, these values are defined within ios_base, which is a base class for ios.

adjustfieldbasefieldboolalpha
decfixedfloatfield
hexinternalleft
octright scientific
showbaseshowpointshowpos
skipwsunitbufuppercase

These values are used to set or clear the format flags.

  • When the skipws flag is set, leading white-space characters (spaces, tabs, and newlines) are discarded when performing input on a stream. When skipws is cleared, white-space characters are not discarded.
  • When the left flag is set, output is left justified. When the right is set, output is right justified. When the internal flag is set, a numeric value is padded to fill a field by inserting spaces between any sign or base character. If none of these flags are set, output is right justified by default.
  • By default, numeric values are output in decimal. However, it is possible to change the number base. Setting the oct flag causes output to be displayed in octal. Setting the hex flag causes output to be displayed in hexadecimal. To return output to decimal, set the dec flag.
  • Setting showbase causes the base of numeric values to be shown. For example, if the conversion base is hexadecimal, the value 1F will be displayed as 0x1F.
  • By default, when scientific notation is displayed, the e is in lowercase. Also, when a hexadecimal value is displayed, the x is in lowercase. When uppercase is set, these characters are displayed in uppercase.
  • Setting showpos causes a leading plus sign to be displayed before positive values.
  • Setting showpoint causes a decimal point and trailing zeros to be displayed for all floating-point output - whether needed or not.
  • By setting the scientific flag, floating-point numeric values are displayed using scientific notation. When fixed is set, floating-point values are displayed using normal notation. When neither flag is set, the compiler chooses an appropriate method.
  • When unitbuf is set, the buffer is flushed after each insertion operation.
  • When boolalpha is set, booleans can be input or output using the keywords true and false.

Since it is common to refer to the oct, dec, and hex fields, they can be collectively referred to as basefield. Similarly, the left, right, and internal fields can be referred to as adjustfield. Finally, the scientific and fixed fields can be referenced as floatfield.

To set a flag, use the setf() function. This function is a member of ios. This function returns the previous settings of the format flags (of type fmtflags) and turns on specified flags.

cout.setf(ios::showpoint|ios::showpos);

The complement of setf() is unsetf(). This member function of ios is used to clear one or more format flags.

cout.unsetf(ios::uppercase);

There is an overloaded form of setf() that takes this general form setf(flags1,flags2). In this version, only the flags specified by flags2 are affected. They are first cleared and then set according to the flags specified by flags1. Note that even if flags1 contains other flags, only those specified by flags2 will be affected. The previous flags setting is returned.

cout.setf(ios::hex,ios::basefield);

There will be times when you only want to know the current format settings but not alter any. To accomplish this goal, ios includes the member function flags(), which simply returns the current setting of each format flag (of type fmtflags).

void showFlags (ostream &out, ios &strm) { ios::fmtflags f = (long) strm.flags(); // get flag settings for (long i=0x4000; i; i = i >> 1) // check each flag if (i&f) out << "1 "; else out << "0 "; out << endl; }

The flags() function has a second form that allows you to set all format flags associated with a stream. When you use this version, the bit pattern found in its argument is used to set the format flags associated with the stream. Thus, all format flags are affected. The function returns the previous settings (of type fmtflags).

void doNotChangeFlags (ios &strm) { ios::fmtflags f = (long) strm.flags(); // get flag settings // work with the stream strm strm.flags(f); // restore flag settings }
Using width(), precision(), and fill()
Overloading operator<< and operator>>
Using manipulators to format I/O
Creating your own manipulators
Unformated I/O

The class ostream is defined with the operator << (put to) to handle output of the built-in types. The << operator is the most useful part of output streams. However, there is additional functionality: public methods put() and write(). The put() and write() functions simply write characters.

Input is handled similarly to output. There is a class istream that provides an input operator >> (get from) for a small set of standard types. The >> operator is intended for formatted input. Where this is not desirable and we want to read characters as characters and then examine them, we use the get() or getline() functions. The get() and getline() functions treat whitespace characters exactly like other characters. They are intended for input operations, where one doesn't make assumptions about the meanings of the characters read. We prefer getline() over get(). A getline() behaves like its corresponding get(), except that it removes its terminator from the istream.

When efficiency isn't paramount, it is better to read into a string. In that way, the most common allocation and overflow problems cannot occur. However, the get(), getline() and read() functions are needed to implement such higher-level facilities. The ignore() function reads characters like read(), but it doesn't store them anywhere.

Stream state

Every stream (istream or ostream) has a state associated with it. Errors and nonstandard conditions are handled by setting and testing this state appropriately. The stream state is found in basic_istream's base basic_ios from <ios>:

template <class Ch, class Tr=char_traits<Ch> > class basic_ios : public ios_base { public: // ... bool good () const; // next operation might succeed bool eof () const; // end of input seen bool fail () const; // next operation will fail bool bad () const; // stream is corrupted iostate rdstate () const; // get io state flags void clear (iostate f=goodbit); // set io state flags void setstate (iostate f) { clear(rdstate()|f); } // add f to io state flags operator void* () const; // nonzero if !fail() bool operator! () const { return fail(); } // ... };

If the state is good() the previous input operation succeeded. If the state is good(), the next input operation might succeed; otherwise, it will fail. Applying an input operation to a stream that is not in the good() state is a null operation. If we try to read into a variable v and the operation fails, the value of v should be unchanged (it is unchanged if v is a variable of one of the types handled by istream or ostream member functions). The difference between the states fail() and bad() is subtle. When the state is fail() but not also bad(), it is assumed that the stream is uncorrupted and that no characters have been lost. When the state is bad(), all bets are off.

The state of a stream is represented as a set of flags. Like most constants used to express the behavior of streams, these flags are defined in basic_ios' base ios_base:

class ios_base { public: // ... typedef implementation_defined2 iostate; static const iostate badbit, // stream is corrupted eofbit, // end-of-file seen failbit, // next operation will fail goodbit; // goodbit==0 // ... };

The I/O state flags can be directly manipulated.

void f (istream &in) { ios_base::iostate s = in.rdstate(); // returns a set of iostate bits if (s & ios_base::badbit) { // cin characters possibly lost } else { // do something with the input stream } in.setstate(s | ios_base::failbit); };

When a stream is used as a condition, the state of the stream is tested by operator void* or operator !. The test succeeds only if the state is good().

Exceptions in streams

It is not convenient to test for errors after each I/O operation, so a common cause of error is failing to do so where it matters. In particular, output operations are typically unchecked, but they do occasionally fail.

The only function that directly changes the state of a stream is clear(). Thus, an obvious way of getting notified by a state change is to ask clear() to throw an exception. The ios_base member exceptions() does just that:

template <class Ch, class Tr=char_traits<Ch> > class basic_ios : public ios_base { public: // ... class failure; // exception class iostate exceptions () const; // get exception state void exceptions (iostate except); // set exception state // ... };

For example,

cout.exceptions(ios_base::badbit|ios_base::failbit|ios_base:eofbit);

requests that clear() should throw an ios_base::failure exception if cout goes into states bad, fail or eof - in other words, if any output operation on cout doesn't perform flawlessly. Similarly,

cin.exceptions(ios_base::badbit|ios_base::failbit);

allows us to catch the not-too-uncommon case in which the input is not in the format we expected, so an input operation didn't return a value from the stream.

A call of exceptions() with no arguments returns the set of I/O state flags that triggers an exception. The primary use of I/O exceptions is to catch unlikely - and therefore often forgotten - errors. Another is to control I/O.

File streams

Files lend themselves very well to the stream abstraction because reading and writing files always involves a position in addition to the data. In C++, the ofstream and ifstream classes provide output and input functionality for files. They are defined in the header file <fstream>.

Here is a complete program that copies one file to another. The file names are taken as commandline arguments.

# include <cstdlib> # include <fstream> using namespace std; void error (const char *m, const char *f="") { cerr << m << ' ' << f << endl; exit(1); } int main (int argc, char *argv[]) { if (argc!=3) error("wrong number of arguments"); ifstream from(argv[1]); // open input file stream if (!from) error("cannot open input file",argv[1]); ofstream to(argv[2]); // open output file stream if (!to) error("cannot open output file",argv[2]); for (char ch=0; from.get(ch); to.put(ch)); if (!from.eof()||!to) error("something strange happened"); return 0; }

A file is opened for input by creating an object of class ifstream (input file stream) with the file name as the argument. Similarly, a file is opened for output by creating an object of class ofstream (output file stream) with the file name as the argument. In both cases, we test the state of the created object to see if the file was successfully opened.

A file can be explicitly closed by calling close() on its stream. However, this is implicitly done by the stream's destructor. So an explicit call of close() is needed only if the file must be closed before reaching the end of the scope in which its stream was declared.

String streams

A stream can be attached to a string. That is, we can read from a string and write to a string using the formatting facilities provided by streams. Such streams are called a stringstreams. They are defined in the header file <sstream>.

An ostringstream can be used to format message strings. An istringstream is an input stream reading from a string.


References
  1. B.Stroustrup: The C++ programming language. Third edition.
    Section 21: streams; pp. 605-654.
  2. H. Schildt: C++: the complete reference. Third edition.
    Section 20: the C++ I/O system basics; pp. 511-540.
    Section 21: the C++ file I/O; pp. 541-568.