The Stream Library: A Primer

The stream library provides a group of classes that supports convenient data exchange between a program and the external world, based on the file abstraction in the OS. From this aspect, stream is a higher level of abstraction developed by C++. The stream library itself is very useful, however, studying how the stream classes are structured also may offer deeper insights of object oriented programming in general. However, due to variations among different implementations, it is a little tricky to write portable code using the stream library, if some uncommon manipulators are called. But at any rate, for common usage and to learn more about object oriented programming, we should understand how stream works.

The Inheritance Structure 

A file abstraction in the OS can be considered as an unstructured byte sequence. A file can be a collection of byte blocks on a disk, or an I/O device. The stream concept is more general, in that it considers any data flowing from an object to another object as a stream. It does not matter whether the data is buffered or reformatted.

 

Before a steam is used, it must be created, like any object, and thus it needs to be deleted after usage. From a stream, both extraction and insertion operators can be performed.

ios defines basic operations to manipulate the status of a stream, such as formats during extraction and insertion, as well as error status. ios is the base class for all the input/output stream classes. While ios is not technically an abstract base class, you will not usually construct ios objects, nor will you derive classes directly from ios. Instead, you will use the derived classes istream and ostream or other derived classes. Even though you will not use ios directly, you will be using many of the inherited member functions and data members described here. Remember that these inherited member function descriptions are not duplicated for derived classes.

ios è streambuf

The streambuf class provides support for ios class and it’s derived classes.

ios (the Microsoft implementation)

Data Members (static) — Public Members

basefield: Mask for obtaining the conversion base flags (dec, oct, or hex).

adjustfield: Mask for obtaining the field padding flags (left, right, or internal).

floatfield: Mask for obtaining the numeric format (scientific or fixed).

Construction/Destruction — Public Members

ios: Constructor for use in derived classes.

~ios: Virtual destructor.

Flag and Format Access Functions — Public Members

flags: Sets or reads the stream’s format flags.

setf: Manipulates the stream’s format flags.

unsetf: Clears the stream’s format flags.

fill: Sets or reads the stream’s fill character.

precision: Sets or reads the stream’s floating-point format display precision.

width: Sets or reads the stream’s output field width.

Status-Testing Functions — Public Members

good: Indicates good stream status.

bad: Indicates a serious I/O error.

eof: Indicates end of file.

fail: Indicates a serious I/O error or a possibly recoverable I/O formatting error.

rdstate: Returns the stream’s error flags.

clear: Sets or clears the stream’s error flags.

User-Defined Format Flags — Public Members

bitalloc: Provides a mask for an unused format bit in the stream’s private flags variable (static function).

xalloc: Provides an index to an unused word in an array reserved for special-purpose stream state variables (static function).

iword: Converts the index provided by xalloc to a reference (valid only until the next xalloc).

pword: Converts the index provided by xalloc to a pointer (valid only until the next xalloc).

Other Functions — Public Members

delbuf: Controls the connection of streambuf deletion with ios destruction.

rdbuf: Gets the stream’s streambuf object.

sync_with_stdio: Synchronizes the predefined objects cin, cout, cerr, and clog with the standard I/O system.

tie: Ties a specified ostream to this stream.

Operators — Public Members

operator void*: Converts a stream to a pointer that can be used only for error checking.

operator !: Returns a nonzero value if a stream I/O error occurs.

ios Manipulators

dec: Causes the interpretation of subsequent fields in decimal format (the default mode).

hex: Causes the interpretation of subsequent fields in hexadecimal format.

oct: Causes the interpretation of subsequent fields in octal format.

binary: Sets the stream’s mode to binary (stream must have an associated filebuf buffer).

text: Sets the stream’s mode to text,  the default mode (stream must have an associated filebuf buffer).

Parameterized Manipulators (#include <iomanip.h> required)

setiosflags: Sets the stream’s format flags.

resetiosflags: Resets the stream’s format flags.

setfill: Sets the stream’s fill character.

setprecision: Sets the stream’s floating-point display precision.

setw: Sets the stream’s field width (for the next field only).

streambuf

All the iostream classes in the ios hierarchy depend on an attached streambuf class for the actual I/O processing. This class is an abstract class, but the iostream class library contains the following derived buffer classes for use with streams:

-       filebuf   Buffered disk file I/O.

-       strstreambuf   Stream data held entirely within an in-memory byte array.

-       stdiobuf   Disk I/O with buffering done by the underlying standard I/O system.

 

All streambuf objects, when configured for buffered processing, maintain a fixed memory buffer, called a reserve area, that can be dynamically partitioned into a get area for input, and a put area for output. These areas may or may not overlap. With the protected member functions, you can access and manipulate a get pointer for character retrieval and a put pointer for character storage. The exact behavior of the buffers and pointers depends on the implementation of the derived class.

 

The capabilities of the iostream classes can be extended significantly through the derivation of new streambuf classes. The ios class tree supplies the programming interface and all formatting features, but the streambuf class does the real work. The ios classes call the streambuf public members, including a set of virtual functions. The streambuf class does all the dirty low level system work will requires quite a amount of knowledge about operating system, which is beyond our topic.

istream, ostream and isostream

istream defines basic capability for sequential and random access input by inserting data into the stream. An istream object has a streambuf-derived object attached, and the two classes work together; the istream class does the formatting, and the streambuf class does the low-level buffered input. You can use istream objects for sequential disk input if you first construct an appropriate filebuf object. More often, you will use the predefined stream object cin (which is actually an object of class istream_withassign), or you will use objects of classes ifstream (disk file streams) and istrstream (string streams).

Input Functions — Public Members

get: Extracts characters from the stream up to, but not including, delimiters.

getline: Extracts characters from the stream (extracts and discards delimiters).

read: Extracts data from the stream.

ignore: Extracts and discards characters.

peek: Returns a character without extracting it from the stream.

eatwhite: Extracts leading white space.

Other Functions — Public Members

putback: Puts characters back to the stream.

sync: Synchronizes the stream buffer with the external source of characters.

seekg: Changes the stream’s get pointer.

tellg: Gets the value of the stream’s get pointer.

Operators — Public Members

operator >>: Extraction operator for various types.

 

ostream is pretty much another istream, just that ostream does output. Two often used predefined ostream objects include cout and cerr.

Unformatted Output — Public Members

put: Inserts a single byte into the stream.

write: Inserts a series of bytes into the stream.

Other Functions — Public Members

flush: Flushes the buffer associated with this stream.

seekp: Changes the stream’s put pointer.

tellp: Gets the value of the stream’s put pointer.

Operators — Public Members

operator <<: Insertion operator for various types.

Manipulators

endl: Inserts a newline sequence and flushes the buffer.

ends: Inserts a null character to terminate a string.

flush: Flushes the stream’s buffer.

 

The iostream class provides the basic capability for sequential and random-access I/O. It inherits functionality from the istream and ostream classes. The iostream class works in conjunction with classes derived from streambuf (for example, filebuf). In fact, most of the iostream “personality” comes from its attached streambuf class. You can use iostream objects for sequential disk I/O if you first construct an appropriate filebuf object. More often, you will use objects of classes fstream and strstream.

 

Manipulators are a group of functions that change the format or status of a stream. You can call a manipulator by either xxostream.setw(20) or xxostream<<setw(20)<<” ….”<<endl;, suppose xxostream is an output stream. To use manipulators, need to include iomanip.h, if you are using iostream.h. Many manipulators are defined for the ios stream and thus affect both istream and ostream. But few manipulators actually affect istreams, except the radix manipulator (dec, oct, hex).

 

#include <iostream.h>

void main()

{

   double values[] = { 1.23, 35.36, 653.7, 4358.24 };

   for( int i = 0; i < 4; i++ )

   {

      cout.width(10);

      cout << values[i] << '\n';

   }

}

The output looks like this:

      1.23

     35.36

     653.7

   4358.24

 

Now, the endl manipulator replaces the newline character ('\n').

for( int i = 0; i < 4; i++ )

{

   cout.width( 10 );

   cout.fill( '*' );

   cout << values[i] << endl

}

The output looks like this:

******1.23

*****35.36

*****653.7

***4358.24

 

#include <iostream.h>

#include <iomanip.h>

void main()

{

   double values[] = { 1.23, 35.36, 653.7, 4358.24 };

   char *names[] = { "Zoot", "Jimmy", "Al", "Stan" };

   for( int i = 0; i < 4; i++ )

      cout << setw( 6 )  << names[i]

           << setw( 10 ) << values[i] << endl;

}

 

The width member function is declared in IOSTREAM.H. If you use setw or any other manipulator with arguments, you must include IOMANIP.H. In the output, strings are printed in a field of width 6 and integers in a field of width 10:

 Zoot       1.23

Jimmy      35.36

   Al      653.7

 Stan    4358.24

 

Output streams default to right-aligned text. To left align the names in the previous example and right align the numbers, replace the for loop as follows:

for ( int i = 0; i < 4; i++ )

   cout << setiosflags( ios::left )

        << setw( 6 )  << names[i]

        << resetiosflags( ios::left )

        << setw( 10 ) << values[i] << endl;

 

The output looks like this:

Zoot        1.23
Jimmy      35.36
Al         653.7
Stan     4358.24

 

The left-align flag is set by using the setiosflags manipulator with the ios::left enumerator. This enumerator is defined in the ios class, so its reference must include the ios:: prefix. The resetiosflags manipulator turns off the left-align flag. Unlike width and setw, the effect of setiosflags and resetiosflags is permanent.

 

The default value for floating-point precision is six. For example, the number 3466.9768 prints as 3466.98. To change the way this value prints, use the setprecision manipulator. The manipulator has two flags, ios::fixed and ios::scientific. If ios::fixed is set, the number prints as 3466.976800. If ios::scientific is set, it prints as 3.4669773+003. To display the floating-point numbers with one significant digit, replace the for loop as follows:

for ( int i = 0; i < 4; i++ )

   cout << setiosflags( ios::left )

        << setw( 6 ) 

        << names[i]

        << resetiosflags( ios::left )

        << setw( 10 )

        << setprecision( 1 )

        << values[i]

        << endl;

 

The program prints this list:

Zoot          1
Jimmy     4e+001
Al        7e+002
Stan      4e+003

 

To eliminate scientific notation, insert this statement before the for loop:

cout << setiosflags( ios::fixed );

 

With fixed notation, the program prints with one digit after the decimal point.

Zoot         1.2

Jimmy       35.4

Al         653.7

Stan      4358.2

 

If you change the ios::fixed flag to ios::scientific, the program prints this:

Zoot    1.2e+000
Jimmy   3.5e+001
Al      6.5e+002
Stan    4.4e+003

 

Again, the program prints one digit after the decimal point. If either ios::fixed or ios::scientific is set, the precision value determines the number of digits after the decimal point. If neither flag is set, the precision value determines the total number of significant digits. The resetiosflags manipulator clears these flags.

 

The dec, oct, and hex manipulator set the default radix for input and output. For example, if you insert the hex manipulator into the output stream, the object correctly translates the internal data representation of integers into a hexadecimal output format. The default radix is dec (decimal).

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

 

Since one purpose of this lecture is about learning from how the stream library is developed, appended below are some excerpts from the ios family header files (note, I got these from linux).

 

struct _ios_fields

{ // The data members of an ios.

    streambuf *_strbuf;

    ostream* _tie;

    int _width;

    __fmtflags _flags;

    _IO_wchar_t _fill;

    __iostate _state;

    __iostate _exceptions;

    int _precision;

 

    void *_arrays; /* Support for ios::iword and ios::pword. */

};

 

class ios : public _ios_fields {

  ios& operator=(ios&);  /* Not allowed! */

  ios (const ios&); /* Not allowed! */

  public:

    typedef __fmtflags fmtflags;

    typedef int iostate;

    typedef int openmode;

    typedef _IO_ssize_t streamsize;

    enum io_state {

        goodbit = _IOS_GOOD,

        eofbit = _IOS_EOF,

        failbit = _IOS_FAIL,

        badbit = _IOS_BAD };

    enum open_mode {

        in = _IO_INPUT,

        out = _IO_OUTPUT,

        ate = _IO_ATEND,

        app = _IO_APPEND,

        trunc = _IO_TRUNC,

        nocreate = _IO_NOCREATE,

        noreplace = _IO_NOREPLACE,

        bin = _IOS_BIN, // Deprecated - ANSI uses ios::binary.

        binary = _IOS_BIN };

    enum seek_dir { beg, cur, end};

    typedef enum seek_dir seekdir;

    // NOTE: If adding flags here, before to update ios::bitalloc().

    enum { skipws=_IO_SKIPWS,

           left=_IO_LEFT, right=_IO_RIGHT, internal=_IO_INTERNAL,

           dec=_IO_DEC, oct=_IO_OCT, hex=_IO_HEX,

           showbase=_IO_SHOWBASE, showpoint=_IO_SHOWPOINT,

           uppercase=_IO_UPPERCASE, showpos=_IO_SHOWPOS,

           scientific=_IO_SCIENTIFIC, fixed=_IO_FIXED,

           unitbuf=_IO_UNITBUF, stdio=_IO_STDIO

           };

    enum { // Masks.

        Basefield = dec+oct+hex,

        floatfield = scientific+fixed,

        adjustfield = left+right+internal

    };

 

    ostream* tie() const { return _tie; }

    ostream* tie(ostream* val) { ostream* save=_tie; _tie=val; return save; }

 

    // Methods to change the format state.

    _IO_wchar_t fill() const { return _fill; }

    _IO_wchar_t fill(_IO_wchar_t newf)

        {_IO_wchar_t oldf = _fill; _fill = newf; return oldf;}

    fmtflags flags() const { return _flags; }

    fmtflags flags(fmtflags new_val) {

        fmtflags old_val = _flags; _flags = new_val; return old_val; }

    int precision() const { return _precision; }

    int precision(int newp) {

        unsigned short oldp = _precision; _precision = (unsigned short)newp;

        return oldp; }

    fmtflags setf(fmtflags val) {

        fmtflags oldbits = _flags;

        _flags |= val; return oldbits; }

    fmtflags setf(fmtflags val, fmtflags mask) {

        fmtflags oldbits = _flags;

        _flags = (_flags & ~mask) | (val & mask); return oldbits; }

    fmtflags unsetf(fmtflags mask) {

        fmtflags oldbits = _flags;

        _flags &= ~mask; return oldbits; }

    int width() const { return _width; }

    int width(int val) { int save = _width; _width = val; return save; }

    void clear(iostate state = 0) {

        _state = _strbuf ? state : state|badbit;

        if (_state & _exceptions) _throw_failure(); }

    void set(iostate flag) { _state |= flag;

        if (_state & _exceptions) _throw_failure(); }

    void setstate(iostate flag) { _state |= flag; // ANSI

        if (_state & _exceptions) _throw_failure(); }

    int good() const { return _state == 0; }

    int eof() const { return _state & ios::eofbit; }

    int fail() const { return _state & (ios::badbit|ios::failbit); }

    int bad() const { return _state & ios::badbit; }

    iostate rdstate() const { return _state; }

    operator void*() const { return fail() ? (void*)0 : (void*)(-1); }

    int operator!() const { return fail(); }

    iostate exceptions() const { return _exceptions; }

    void exceptions(iostate enable) {

        _exceptions = enable;

        if (_state & _exceptions) _throw_failure(); }

 

    streambuf* rdbuf() const { return _strbuf; }

    streambuf* rdbuf(streambuf *_s) {

      streambuf *_old = _strbuf; _strbuf = _s; clear (); return _old; }

 

    static int sync_with_stdio(int on);

    static void sync_with_stdio() { sync_with_stdio(1); }

    static fmtflags bitalloc();

    static int xalloc();

    void*& pword(int);

    void* pword(int) const;

    long& iword(int);

    long iword(int) const;

 

  protected:

    inline ios(streambuf* sb = 0, ostream* tie_to = 0);

    inline virtual ~ios();

    inline void init(streambuf* sb, ostream* tie = 0);

};

 

Setting radix becomes a call like: (not all three calls exist and not all are manipulators in every C++ implementation!)

 

cin.setiosflags(ios::hex);           cin>>setiosflags(ios::hex);

cin.setbase(16);                     cin>>setbase(16);

cin.setf(ios::hex, ios::basefield);  cin>>setf(ios::hex,ios::basefield);

enum

enum [tag] {enum-list} [declarator];   // for definition of enumerated type

enum tag declarator;   // for declaration of variable of type tag

 

The enum keyword specifies an enumerated type.

 

An enumerated type is a user-defined type consisting of a set of named constants called enumerators. By default, the first enumerator has a value of 0, and each successive enumerator is one larger than the value of the previous one, unless you explicitly specify a value for a particular enumerator. Enumerators needn’t have unique values. The name of each enumerator is treated as a constant and must be unique within the scope where the enum is defined. An enumerator can be promoted to an integer value. However, converting an integer to an enumerator requires an explicit cast, and the results are not defined.

 

In C, you can use the enum keyword and the tag to declare variables of the enumerated type. In C++, you can use the tag alone.

 

In C++, enumerators defined within a class are accessible only to member functions of that class unless qualified with the class name (for example, class_name::enumerator). You can use the same syntax for explicit access to the type name (class_name::tag).

 

Let’s see if you learnt anything

Now, can someone tell me how “attaching streambuf and ios classes” was implemented, and maybe why manipulators are needed and how are those implemented? How does ios classes keep track of all the formatting information?