home *** CD-ROM | disk | FTP | other *** search
/ Collection of Hack-Phreak Scene Programs / cleanhpvac.zip / cleanhpvac / IOSTUTOR.ZIP / iostutor.txt
Text File  |  1995-03-14  |  13KB  |  261 lines

  1. PROLOGUE --------
  2.  
  3. This tutorial started out as a "small" example of how to interface an I/O
  4. object into the AT&T iostream classes currently provided with almost every
  5. C++ compiler. When I say "small", I mean just that - but the scope of the
  6. information I wanted to present really didn't fit well into that little hole,
  7. so it turned out a little larger than I had initially hoped. However, I think
  8. the effort was worth it - I know it was for me, since although I've done code
  9. which deals with ostream, I hadn't fooled at all with istream derived
  10. facilities, and learned some new things in the process.
  11.  
  12. I can't really cite any single reference on iostreams that might prove useful
  13. for those wanting to go further other than the AT&T iostreams reference
  14. manual (my copy is out on loan at the moment so I can't quote the ISBN
  15. number). I have never come across one which is more complete than this
  16. reference, and most other references (including documentation from MSC/C++C,
  17. Borland, IBM etc.) tend to quote from it fairly liberally, but lack any of
  18. the important information needed to put it all together. While Microsoft's
  19. C++ tutorial reference has some great hints for getting started in iostream
  20. manipulators, it lacks in providing any information on interfacing to the
  21. iostream classes themselves.
  22.  
  23. Hopefully the information I've provided below will make some sense. It is as
  24. complete as I could make it without really going overboard. Most of the
  25. ostream related code is gleaned from my own library, but the rest is brand
  26. new.
  27.  
  28. The text of this tutorial and the accompanying source code are donated to the
  29. public domain.
  30.  
  31.  
  32. Tutorial in iostreams ---------------------
  33.  
  34. This little project started out with the following aims:
  35.  
  36. A)  To define a simple input/output object, one that consumes bytes sent to
  37.     it and generates data for input into a sample program. It should there-
  38.     fore feature a "read" and "write" function.  The object created is simply
  39.     a loopback buffer - input is queued immediately for output in FIFO (first
  40.     in first out) fashion.
  41.  
  42. B)  To create a streams interface for this object, so that existing facili-
  43.     ties for I/O in the AT&T streams classes can be used directly in a
  44.     polymorphic fashion (ie. make use of C++ inheritance & virtual dispatch).
  45.  
  46.     Specifically, I wanted to demonstrate the following aspects of iostreams:
  47.  
  48.     - Use of buffered vs. unbuffered I/O
  49.  
  50.     - Using the streams buffer for both input and output operations.  (an
  51.       interface to iostream, rather than just istream or ostream)
  52.  
  53.     - How the put, get and putback buffers work
  54.  
  55. C)  To write a trivial application which uses both components and provide a
  56.     means of interactively demonstrating how it all works.
  57.  
  58.  
  59. A - The I/O Object ----------------
  60.  
  61. class Myio;
  62.  
  63. This class is simply a front-end for a circular buffer. Input bytes are added
  64. at the head, and read from the tail. It is fixed in size, set by the
  65. constructor.
  66.  
  67. Two read/write functions are provided to access any contained data:
  68.  
  69.     int Myio::read (char * buf, int max);
  70.     int Myio::write (char const * buf, int len);
  71.  
  72. These are both non-blocking calls which return the number of bytes read or
  73. written. They know nothing about line delineation - only about raw bytes, as
  74. would be the case for almost any I/O device.
  75.  
  76. In addition, an internal flag is maintained to indicate when a write results
  77. in a buffer 'overflow' (an attempt to write more bytes than will fit in the
  78. buffer) and 'underflow' (an attempt to read an empty buffer). These flags
  79. reflect the last write and read calls respectively, and are reset or set on
  80. each write or read call.  The members Myio::readok() and Myio::writeok()
  81. rturn the settings as a boolean value.
  82.  
  83. A Myio object can also optionally create a stream. It is created and comes
  84. into life when the member function Myio::stream() is called. If it was
  85. previously created, this function simply returns a reference to the existing
  86. stream. The stream, if it exists, is deleted by the destructor.
  87.  
  88. Myio's stream is an iostream, which inherits all of the abilities of both
  89. ostream (for output) and istream (for input), including all operators. This,
  90. of course, is the primary benefit of using streams!
  91.  
  92.  
  93. B -  The Streams Interface -----------------------
  94.  
  95. class Mystreambuf; class Mystreambase; class Mystream;
  96.  
  97. Three classes as above are used. Mystreambuf derives from streambuf, and is
  98. responsible for the input output operations and buffering on behalf of a Myio
  99. object.  Mystreambase is used as a base class for Mystream to assist in the
  100. initialisation of the (My)streambuf passed to the iostream constructor.
  101.  
  102. The iostream side is in fact very simple. Nothing really needs to be
  103. overridden, and all of the work is done in Mystreambuf, where all the action
  104. really takes place.
  105.  
  106. The relationship between the ios/stream classes and the streambuf family is
  107. one of delegation rather than inheritance. The user/application accesses
  108. streambuf I/O via the stream object, not directly.  A class diagram showing
  109. the basic iostream classes and our classes would look like:
  110.  
  111.  
  112.          _ istream _ / \ ios -- ostream -- iostream \ \ \ Mystream \_____
  113. Mystreambase _/ | | (owns) | streambuf -- Mystreambuf
  114.  
  115.  
  116. All relationships, except the one marked "(owns)", indicate inheritance. The
  117. 'owns' relationship is where the delegation occurs.  ios is inherited
  118. virtually, so that there is only one combined 'ios' object at the root of the
  119. streams inheritence tree.
  120.  
  121. Within Mystreambuf, we need to override the functions responsible for actual
  122. input and output.  But first, let's discuss how this streambuf works.
  123.  
  124. Mystreambuf uses a single buffer, using the default buffer size allocated for
  125. any streambuf (under most operating systems, this will be 1024 bytes). Since
  126. we are dealing with both input and output operations, and these operations
  127. are independent so far as the streambuf is concerned (as is the case with,
  128. for example, serial I/O, but *not* the case with files), the buffer is split
  129. into two; the first half is used for writing, the second for reading.
  130.  
  131. The buffer used for writing is called the "put" buffer. Nothing mysterious
  132. there - when full, streambuf::overflow() function is called, and via virtual
  133. dispatch calls our Mystreambuf::overflow() which takes the contents of the
  134. buffer and writes it to the output device.
  135.  
  136. The read - or "get" - buffer is slightly more complex. There are occasions in
  137. dealing with an input stream where it is convenient to know what's next
  138. without actually removing it from the stream.  That way, you can use the next
  139. character as an indication of what to do next. For example, if you're parsing
  140. a number, you want to know whether or not the next character is a valid
  141. digit, and stop parsing if it isn't. The read side therefore incorporates the
  142. idea of a "putback" buffer - after being read, the character can be placed
  143. back into the input stream.
  144.  
  145. The putback buffer is entirely the responsibility of any streambuf derived
  146. class. It most you need to support a one character putback buffer - it is not
  147. valid to remove, and then restore, more than one character from the stream.
  148. It is also not valid put 'putback' any character but the one that was the
  149. result of the last 'get'.  It really must be "put back", not any old
  150. character "pushed" (you could actually support 'pushing' data into the stream
  151. if you wanted to, but you shouldn't use putback to do it).
  152.  
  153. The get buffer is set up as:
  154.  
  155.     Offset 0 1 2 3 ....  n | | | | to end of buffer |
  156. +---+---+---+---------------------+ ^ ^ | +- Start of get buffer (where data
  157. is read in) | +- Where data is putback
  158.  
  159. Each time streambuf runs out of characters to give to its client, the
  160. underflow() function is called. This fills the get buffer (get buffer size -
  161. 1, starting at offset 1) and reserves the first byte in case the previously
  162. read character needs to be put back.
  163.  
  164. streambuf provides internal pointers into the put, get and putback areas. All
  165. of the I/O functions it provides handle these automatically. In our
  166. underflow() and overflow() functions, we need to set these pointers according
  167. to where and how much data is read in.
  168.  
  169. I mentioned above that in our case, the input & output streams are
  170. independant. That's not entirely the case - it may happen that when reading
  171. from the Myio buffer we run out of data and need additional data in the
  172. output stream buffer not yet written to Myio. We therefore flush the output
  173. stream before retrieving any data by calling overflow() directly from within
  174. underflow().
  175.  
  176. The sync() function is also overridden. This simply empties all buffers by
  177. flushing the output buffer and discarding any buffered input.
  178.  
  179.  
  180. C - The Application -----------------
  181.  
  182. The application itself is a simple menu, offering choice to send a line of
  183. output to the IO object (via its stream), read one in, and dump/display
  184. information both about the stream and Myio object.
  185.  
  186. This added two other classes to the project:
  187.  
  188.     - myApplication: the actual application, implemented as a class.  The
  189.       only way to go in C++. :-)
  190.  
  191.     - myList: a simple line input class, whose sole purpose in life is to
  192.       extract a linefeed delimited line from any istream object and return it
  193.     as a char const *.  (I posted this code last week, but have since fixed
  194.     one minor bug I found in the process of developing Myio).
  195.  
  196. A couple of subtle points - class myApplication uses a pointer to member
  197. function it its menu selection. This is not the only way of doing this of
  198. course, but I thought it was a good way of demonstrating a very C++ specific
  199. concept, operator ->*, which does not exist at all in C.
  200.  
  201. Additional notes are included in the source comments.
  202.  
  203.  
  204. Making the application ----------------------
  205.  
  206. Hopefully this is a fairly simple thing to do - just compile the modules:
  207.  
  208.     Myiodemo.cpp Myio.cpp Mystream.cpp myLine.cpp
  209.  
  210. and link them together. A simple makefile is provided - take a look at the
  211. definitions at the top, adjust as desired, and type "make" (or nmake). If you
  212. use any of Borland's compilers, just add the above files to a new project
  213. called "Myiodemo.PRJ", set it to produce a .EXE (*not* Windows or PM based)
  214. and press F9.
  215.  
  216. Assuming a C++ compiler compatibile with cfront 2.1 and the presence of an
  217. iostreans 1.2 library, the only non-portable part of this app is the use of
  218. getch() from conio.h. This isn't easily provided under a UNIX system. You can
  219. either fudge it by writing a getch() which switches into/out of 'raw' mode,
  220. or use getchar() and clear everything up to and including a CR or NL after
  221. the first character (the user still has to hit CR for input to get to the
  222. program).
  223.  
  224.  
  225.  
  226. EPILOGUE --------
  227.  
  228. Just some notes as to use of this code. If you need an output or input only
  229. class, then you use ostream or istream wherever iostream is mentioned in this
  230. example.  Also, if you use buffered mode (you can support it or not - you can
  231. even ignore the streambuf setting at your discretion), then you can use the
  232. entire buffer rather than just half each for input output.
  233.  
  234. If you interface to an input only object, you only need to override
  235. streambuf::underflow(). Conversely, you override streambuf::overflow() for an
  236. output only object. I have noticed that *some* implementations of iostreams
  237. define the overflow() and underflow() methods as pure virtual functions,
  238. whereas the AT&T default defines each as simply returning EOF.
  239.  
  240. If portability is any concern, you may need to override the function you
  241. aren't using in this fashion. The default sync() simply returns 0 (success),
  242. but again, this is sometimes defined as a pure virtual, so you may need to
  243. define it in your implementation.
  244.  
  245. In some cases, you may wish to "switch" between unbuffered and buffered
  246. modes. This is easily done by defining a function in Mystream which does it,
  247. and this object is of course accessible in your I/O object (in this case
  248. Myio). The only thing you need to remember is to flush all the buffers by
  249. calling sync() when switching from buffered to unbuffered mode.
  250.  
  251. Note also that some streambuf constructors take an existing buffer. This
  252. means that you can use buffers already provided in your I/O object directly
  253. rather than being forced to "double buffer" anything.  Your buffer can also
  254. be any size you like, subject to memory and other architecture constraints.
  255.  
  256.  
  257. Enjoy!
  258.  
  259. David Nugent - 3:632/348@fidonet.org
  260. Moderor ('93-'94) of the FidoNet international C++ EchoMail conference
  261.