Defining Your Own Format State Flags

If you have defined your own input or output operator for a class type, you may want to offer some flexibility in how you handle input or output of instances of that class. The I/O Stream Classes let you define stream-specific flags that you can then use with the format state member functions such as setf() and unsetf(). You can then code checks for these flags in the input and output operators you write for your class types, and determine how to handle input and output according to the settings of those flags.

For example, suppose you develop a program that processes customer names and addresses. In the original program, the postal code for each customer is written to the output file before the country name. However, because of postal regulations, you are instructed to change the record order so that the postal code appears after the country name. The following example shows a program that translates from the old file format to the new file format, or from the new file format to the old.

The program checks the input file for an exclamation mark as the first byte. If one is found, the input file is in the new format, and the output file should be in the old format. Otherwise the reverse is true. Once the program knows which file should be in which format, it requests a free flag from each file's stream object. It reads in and writes out each record, and closes the file. The input and output operators for the class check the format state for the defined flag, and order their output accordingly.

// Defining your own format flags
#include <fstream.h>
#include <stdlib.h>
long InFileFormat=0;
long OutFileFormat=0; 
class CustRecord {
   public:
      int Number;
      char Name[48];
      char Phone[16];
      char Street[128];
      char City[64];
      char Country[64];
      char PostCode[10];
   };
ostream& operator<<(ostream &os, CustRecord &cust) {
   os << cust.Number << '\n'
      << cust.Name << '\n'
      << cust.Phone << '\n'
      << cust.Street << '\n'
      << cust.City << '\n';
   if (os.flags() & OutFileFormat)    // New file format
      os << cust.Country << '\n'
         << cust.PostCode << endl;
   else                               // Old file format
      os << cust.PostCode << '\n'
         << cust.Country << endl;
   return os;
   }
istream& operator>>(istream &is, CustRecord &cust) {
   is >> cust.Number;
   is.ignore(1000,'\n'); // Ignore anything up to and including new line
   is.getline(cust.Name,48);
   is.getline(cust.Phone,16);
   is.getline(cust.Street,128);
   is.getline(cust.City,64); 
   if (is.flags() & InFileFormat) {   // New file format!
      is.getline(cust.Country,64);
      is.getline(cust.PostCode,10);
      }
   else {
      is.getline(cust.PostCode,10);
      is.getline(cust.Country,64);
      }
   return is;
   }
void main(int argc, char* argv[]) {
   if (argc!=3) {                        // Requires two parameters
      cerr << "Specify an input file and an output file\n";
      exit(1);
      }
   ifstream InFile(argv[1]);
   ofstream OutFile(argv[2],ios::out);
   InFileFormat = InFile.bitalloc();     // Allocate flags for
   OutFileFormat = OutFile.bitalloc();   // each fstream 
   if (InFileFormat==0 ||                // Exit if no flag could
       OutFileFormat==0) {               // be allocated
      cerr << "Could not allocate a user-defined format flag.\n";
      exit(2);
      }
   if (InFile.peek()=='!') {             // '!' means new format
      InFile.setf(InFileFormat);         // Input file is in new format
      OutFile.unsetf(OutFileFormat);     // Output file is in old format
      InFile.get();                      // Clear that first byte
      }
   else {                                // Otherwise, write '!' to
      OutFile << '!';                    // the output file, set the
      OutFile.setf(OutFileFormat);       // output stream's flag, and
      InFile.unsetf(InFileFormat);       // clear the input stream's
      }                                  // flag
   CustRecord record;
   while (InFile.peek()!=EOF) {         // Now read the input file
      InFile >> record;                 // records and write them
      OutFile << record;                // to the output file,
      }
   InFile.close();                     // Close both files
   OutFile.close(); } 

The following shows sample input and output for the program. If you take the output from one run of the program and use it as input in a subsequent run, the output from the later run is the same as the input from the preceding one.

Input File Output File
3848
John Smith
4163341234
35 Baby Point Road
Toronto
M6S 2G2
Canada
1255
Jean Martin
0418375882
48 bis Ave. du Belloy
Le Vesinet
78110
France 
!3848
John Smith
4163341234
35 Baby Point Road
Toronto
Canada
M6S 2G2
1255
Jean Martin
0418375882
48 bis Ave. du Belloy
Le Vesinet
France
78110 

Note that, in this example, a simpler implementation could have been to define a global variable that describes the desired form of output. The problem with such an approach is that later on, if the program is enhanced to support input from or output to a number of different streams simultaneously, all output streams would have to be in the same state (as far as the user-defined format variable is concerned), and all input streams would have to be in the same state. By making the user-defined format flag part of the format state of a stream, you allow formatting to be determined on a stream-by-stream basis.