iostreamio - TextIO Derivative using standard IOstream

Introduction

The STLplus library uses its own TextIO subsystem as it's preferred I/O system. However, the standard C++ I/O system is IOstream. The reasons for replacing IOstream throughout the STLplus are given in the documentation for TextIO.

However, there will be times when the STLplus is used in an application that does use IOstream. This package provides a pair of TextIO wrapper classes that convert IOstream device classes to TextIO device classes.

For example, consider the error handler subsystem. It takes a TextIO Output device as an argument:

class error_handler
{
public:
  error_handler(otext& device,unsigned limit = 0,bool show = true) 
  ...
};

The otext object required by the constructor is the superclass for all output devices in the TextIO subsystem.

What if the user of the error handler wants to use an IOstream device with the error handler?

The answer is to put a TextIO wrapper around the IOstream device using the classes defined in iostreamio.hpp. For example:

#include <fstream>
#include "iostreamio.hpp"
using namespace std;
...
// create and open the IOstream device
ofstream output_stream("errors.log", ios::binary);
// create and initialise the TextIO wrapper device
oiotext output(output_stream);
// now initialise the error handler
error_handler errors(output);

The oiotext object is a subclass of otext. Any text output to this device will be routed to the underlying ofstream class (a subclass of ostream).

The TextIO device stores a reference to the IOstream device. Therefore, as usual in C++, the IOstream device must remain in scope throughout the lifetime of the TextIO device. The above example achieves this by declaring both objects in the same scope. Furthermore, closing the TextIO device does not close the IOstream device, it simply disconnects it.

Note that the iostream device is opened in binary mode. Unfortunately this has to be your responsibility - it doesn't seem to be possible to change the mode of a file after opening it, so it wasn't possible to set it to binary mode within the TextIO device's constructor.

The reason for setting the iostream device to binary mode is so that the TextIO device can take responsibility for line-end handling. For example, if the TextIO device is in DOS line-end mode, then all newlines will be passed to the iostream device as cr/lf pairs. Since the iostream is in binary mode, it will be written to the file in that format. If both TextIO and iostream devices are in text mode, the two subsytems can end up creating a mess (such as double line-ends).

You could if you prefer put the iostream device in text mode and the TextIO device into binary mode instead.

Output Devices

The interface to the output device class is:

class oiotext : public otext
{
public:
  oiotext(std::ostream&);
  void open(std::ostream&);

  std::ostream& get_stream(void);
  const std::ostream& get_stream(void) const;
};

The name of the class is constructed as follows:

oiotext = (o)utput (io)stream (text)io device

This follows the normal convention for TextIO devices: an (o) for output, followed by one or more characters representing the subclass and then the word (text) which is common to all TextIO device classes.

The constructor and the open method do the same thing - they associate the ostream (or a subclass of ostream) with the oiotext device. Any subclass of ostream can be used, not just file objects of class ofstream.

The get_stream methods allow the attached stream to be accessed directly. This will fail catastrophically if there is no device attached (i.e. if the device has not been opened or if it has been closed).

Once an ostream has been attached to the oiotext device, it can be used like any other otext device. For example:

// create and open the IOstream device
ofstream output_stream("output.txt", ios::binary);
// create and initialise the TextIO wrapper device
oiotext output(output_stream);
...
// now use the device
output << "Hello World!" << endl;

This will redirect the text "Hello World!" and a newline to the underlying ostream object (in the example it is called output_stream). This in turn prints the text to the file output.txt.

Input Devices

The interface to the input device class is:

class iiotext : public itext
{
public:
  iiotext(std::istream&);
  void open(std::istream&);

  std::istream& get_stream(void);
  const std::istream& get_stream(void) const;
};

The name of the class is constructed as follows:

iiotext = (i)nput (io)stream (text)io device

This again follows the normal convention for TextIO devices.

The constructor and the open method associate the istream (or a subclass of istream) with the iiotext device. Again, any subclass of istream can be used.

The get_stream methods allow the attached stream to be accessed directly. They will fail catastrophically if there is no device attached.

Once an istream has been attached to the iiotext device, it can be used like any other itext device. For example:

// create and open the IOstream device
ifstream input_stream("data.txt", ios::binary);
// create and initialise the TextIO wrapper device
iiotext input(input_stream);
// now read a series of floats, one per line of the input file
while(input)
{
  float data = 0.0;
  input >> float >> skipendl;
  ...
}

This example reads the data file data.txt which consists of one floating point value per line. Note that, although the file is opened as an IOstream device (specifically, an ifstream), it is read using TextIO functions (for example, the skipendl manipulator).