CS320 Lecture: Input - Output: iostreams, Persistent Objects    10/2/96
                                                        revised 10/1/99
I. The Basic Mechanisms
-  --- ----- ----------

   A. Implementations of the C programming language come with a standard
      library which includes a "standard io" package (#include <sdtio.h>)
      that provides textual input-output for standard data types.

      Because C++ is derived from C, programs written in C++ can use these
      routines as well.

   B. However, the preferred mechanism for textual IO in C++ is to use the more
      object oriented approach provided the the iostreams library which is
      part of all standard C++ implementations.

      1. #include <iostream.h>

        declares: classes istream, ostream (plus common base class ios)

                  variables cin, cout

      2. The following input operations are provided for input streams -
         e.g. cin:

         a. >> for all standard data types (including library class string)
        
                Ex: int i;
        
                    cin >> i;
        
            N.B. Whitespace is skipped and discarded - thus cin >> c will never
                 set c to a space, tab, newline
        
         b. get(), get(char) for single chars:
        
                Ex: char c
        
                    c = cin.get();
                or
                    cin.get(c);
        
            N.B. For all variants of get, whitespace is NOT skipped
        
        c. get(char *, count, delimiter) for C-style strings (array of char
           with null terminator).
        
                Ex: char c[100];
        
                    cin.get(c, 100, '\n');
                or
                    cin.get(c, 100);    // '\n' is default
        
                Does NOT extract the delimiter.
                Does store a null terminator
       
        d. getline(char *, count, delimiter) for C-style strings
        
                Similar to get, but does extract delimiter (but does not store 
                it)
        
        e. getline(stream, string, delimiter) for library class string

           i. Unfortunately, because the library string class was standardized
              after the iostreams library was developed, this is NOT a method
              of class istream, but rather a function that takes an istream as
              its first parameter.

          ii. Ex:

                string s;

                getline(cin, s, '\n');
             or
                getline(cin, s);        // '\n' is the default
                
         iii. NOT: cin.getline(s);      // ERROR

        f. Methods that allow looking ahead one or more characters

            peek()              // Returns character about to be read
            putback(char)       // Puts back a character that was read
            unget()             // Puts back last character that was read

      3. The following output operations are provided for output streams -
         e.g. cout:
        
         a. << for standard data types
        
                Ex: cout << "The answer is " << 42 << " not " << 3.14159;

         b. put(char)
        
         c. flush manipulator - forces output to the terminal immediately
            (otherwise it may be buffered)

                Ex: cout << "This program is about to crash" << flush;

            Note: If text is output to cout and then an attempt is made to
                  read from cin, most implementations do an automatic flush
                  on cout first, assuming that the output was a prompt to
                  the user.

         d. endl manipulator - outputs a newline character and then does a
            flush.

   C. Additional facilities:
        
      1. Manipulators for output streams: #include <iomanip.h>

         declares: manipulators setbase(base) (8, 10, 16)
                                setfill(fillChar)
                                setw(width)
                                -- and many others

                  NB: setw applies only to the NEXT operation on the stream

      2. File input/output: #include <fstream.h>

         a. automatically includes <iostream.h>

         b. declares classes ifstream and ofstream, which inherit from istream
            and ostream and thus get all the above methods discussed for
            cin or cout:

                constructors include

                    ifstream(char *)
                    ofstream(char *)
         
            Note: if filename is in a string object, use, the objects 
                  c_str() method to convert it to a C-style string

            Ex:
                string s;
                cout << "Enter input file name: ";
                cin >> s;
                ifstream infile(s.c_str());

                ... can now do input from infile using << etc.

       3. Treating a C-style string as an input or output stream: 
          #include <strstream.h>

          a. automatically includes <iostream.h>

          b. declares classes istrstream and ostrstream, which inherit from 
             istream and ostream and thus get all the above methods

          c. constructors include

                    istrstream(const char *, int n)
                    ostrstream(char *, int n)

   D. Testing for errors on C++ streams

      1. One important question about any input-output package is how errors
         are handled (especially those due to malformed input).  In some
         languages, IO errors are automatically signalled in some way by the
         language support system.  However, the C++ iostream facility does not
         do this.

      2. Instead, the class ios (which is the base class of istream and
         ostream) maintains a state value which can be good or can record one
         or more of the following:

                fail - an operation failed due to something like malformed input
                bad  - an operation failed due to an io system error
                eof  - (input only) the stream is at end of file - becomes
                       true only AFTER an operation has been tried and failed
                       due to this)

      3. Further, if the state of the stream is anything other than good, any
         operation attempted upon it is ignored.  (The operation returns to
         the caller, but no io occurs)

      4. There are multiple ways of testing the state of a stream, including
         the following:

                stream.good()
                stream.fail()   (True if state is fail and/or bad)
                stream.bad()
                stream.eof()

         It is also possible to test the state of the stream using operator !,
         which is true iff the stream is not good

      5. The method stream.clear() can be used to restore the stream to a good
         state after an error

        Example

          {
            int i;
            cin >> i;
            if (! cin)
              {
                if (cin.eof()) cout << "eof" << endl;
                else if (cin.bad()) cout << "io system error" << endl;
                else cout << "malformed input" << endl;
                cin.clear();
              }
            else
                cout << "You entered: " << i << endl;
          }

II. Overloading iostream operators >>, << for user-defined objects
--  ----------- -------- --------- --  -- --- ------------ -------

   A. iostream.h defines >> and << for standard types as methods of classes
      istream, ostream, with different signatures - e.g. the declaration
      for class istream contains

            istream& operator >> (char*);
            istream& operator >> (char&);
            istream& operator >> (int&);
            istream& operator >> (float&);
            istream& operator >> (double&);

            ostream& operator << (const char *);
            ostream& operator << (char);
            ostream& operator << (int);
            ostream& operator << (float);
            ostream& operator << (double);

      (and many variations to deal with unsigned, long, short etc. versions of
       the above)

   B. To define extractor and inserter operators for our own data types, it
      would appear we would need to make them methods of classes istream or
      ostream.  But we don't want to tamper with the standard header files!

      Solution: define global operator functions

      Example:

        class Rational
          {
            public:

                Rational(int numerator, int denominator);

                ....

                // Note that these are global functions declared as friends,
                // not class members

                friend ostream & operator << (ostream & s, const Rational & r);
                friend istream & operator >> (istream & s, Rational & r);

            private:

                int _numerator,
                    _denominator;
          };
    ...

        ostream & operator << (ostream & s, const Rational & r)
          { s << r._numerator << " / " << r._denominator; 
            return s;                           // NEEDED FOR CHAINING
          }

        istream & operator >> (istream & s, Rational & r)
          { char separator;
            s >> r._numerator >> separator >> r._denominator;
            if (separator != '/') s.set(ios::failbit);
            return s;                           // NEEDED FOR CHAINING
          }

III. Persistent objects
---  ---------- -------

   A. What we will discuss here is not a standardized idea - e.g. not in any
      way a part of the standard C++ library.

   B. A problem: Ordinary C++ objects reside in memory, and hence "live" only
                 during the run of a program.  No way for objects to be
                 preserved between runs of a program.

   C. A Solution:  Create persistent objects that reside on disk, and hence 
                   "live" permanently.  When program is stopped and restarted, 
                   these objects are still there.

                   Example: what objects in video store need persistence?

   D. Methodology: The book discusses one approach to this.  The basic idea is
                   for each class to include an "archive" method which can be
                   used either to save an object of that class to disk or to
                   restore an object from disk.  (Saving/restoring an object
                   entails recursively saving/restoring other objects it 
                   contains or has pointers/references to.)  At program 
                   termination, this method is invoked on the top-level object
                   (e.g. the store itself), resulting in all objects being 
                   saved to disk; when the program is started up again, this 
                   method is invoked to restore the top-level objects,
                   which in turn restores all other objects.

      Note: the Video Store project as distribute incorporates this.

   E. Distributed objects

      1. Carrying the idea of persistence one step further leads to software in
         which the objects involved may be distributed over a network.  
         Individual application programs may start and stop, but the major 
         objects they use have an ongoing existence.  In this case "sending a 
         message to an object" may mean quite literally that.

      2. There is an emerging common industry standard for allowing objects 
         implemented in a variety of different languages on a variety of 
         different platforms to inter-operate with one another.  The standard 
         is known as CORBA - Common Object Request Broker Architecture.

Copyright ©1999 - Russell C. Bjork