Part C - Encapsulation

Custom File Operators

Describe the file stream classes of the standard input output library
Describe the syntax for creating and using file objects with fundamental types
Overload the extraction and insertion operators for file objects and compound types

"Files are examples of containers that you can both read from and write to.  Consequently, you can have a stream that supports both << and >>." (Stroustrup, 1997).

File Classes | File Objects | Fundamental Types | Custom Types | Nice To Know | Exercise


The extraction and insertion operators, which are overloaded for the standard input and output objects in the iostream library, are also overloaded for file objects.  The overloads for file objects cover all of the fundamental types as right operands.  We can overload the operators for objects of compound type as right operands in the same way that we did in the preceding chapter for the standard input and output objects. 

This chapter introduces the stream classes that manage the transfer of information to and from files.  This chapter describes how to create file objects from these classes, how to read and write data of fundamental type and how to overload the operators for file objects as left operands and objects of custom compound type as right operands. 


File Stream Classes

The input-output library defines three classes for managing the transfer of 8-bit character streams between secondary storage and system memory:

  • ifstream - input from a file
  • ofstream - output to a file
  • fstream - input from and output to a file

The fstream system header file includes the class definitions: 

#include <fstream>

The classes are defined within the std namespace.  They provide access to the stream through separate input and output buffers. 

State Methods

The queries available for interrogating a file object include:

  • bool is_open() const - the file stream is open
  • bool good() const - the file stream is ready to process
  • bool fail() const - the file object failed
  • bool eof() const - the file object encountered an end of file mark
  • bool bad() const - the file object encountered a serious error

If a file object is not in a good() state, we must reset its state.  To reset its state we call:

  • void clear() - resets the file object to a good state

These member functions operate in the same way that they operate with the standard input and output objects described in the chapter entitled Input and Output Examples.


File Objects

A file object is an instance of one of the file stream classes.  The object when used with the insertion or extraction operators processes formatted data.  The object uses the platform's encoding sequence (ASCII, EBCDIC, Unicode) to convert between stream bytes and data stored in system memory. 

File Connection

A file object connects to a file for reading and writing in similar ways.  The object's destructor closes the file before disconnecting. 

Input File

Defining an instance of the ifstream class creates a file object for reading.  This class defines a no-argument constructor and one that takes the name of the file in the form of a C-style null-terminated string. 

For example,

 #include <fstream>

 std::ifstream fin("input.txt"); // connect fin to input.txt for input 

To connect a file to an object defined using the no-argument constructor to a file, we call the open() member function. 

For example,

 #include <fstream>

 std::ifstream fin;     // defines an empty file object named fin 
 fin.open("input.txt"); // connect input.txt to fin

Output File

Defining an instance of the ofstream class creates a file object for writing.  This class defines both a no-argument constructor and one that takes the name of the file in the form of a C-style null-terminated string. 

For example,

 #include <fstream>

 std::ofstream fout("output.txt"); // connect fout to output.txt for output 

To connect a file to an object defined using the no-argument constructor, we call the open() member function. 

For example,

 #include <fstream>

 std::ofstream fout;      // create an empty file object named fout 
 fout.open("output.txt"); // connect output.txt to fout

Confirming the Connection

The is_open() member function on a file object returns the current state of the connection:

 #include <iostream>
 #include <fstream>

 std::ofstream fout("output.txt");  // connects output.txt to fout for output 

 if (!fout.is_open()) {
     std::cerr << "File is not open" << std::endl;
 } else {
     // file is open

 }

Fundamental Types

The syntax for reading from a file and writing to one is the same as the syntax for reading from the standard input object and writing to a standard output object (see the chapter entitled Input and Output Examples).

The standard input-output library contains the overloaded definition of the extraction and insertion operators for each of the file stream classes. 

Reading From a File

A file object reads a formatted connection using the extraction operator in the same way as the standard input object (cin) uses this operator. 

For example, consider a file with a single record: 12 34 45 abc  The output from the following program is shown on the right:

 // Reading a File
 // readFile.cpp

 #include <iostream>
 #include <fstream>

 int main() {
     int i;

     std::ifstream f("input.txt");
     if (f.is_open()) {
         while (f.good()) {
             f >> i;
             if (f.good())
                 std::cout << i << ' ';
             else if (!f.eof())
                 std::cout << "\n**Bad input**\n";
         }
     }
 }














 12 34 45

 **Bad input** 



Writing to a File

A file object writes to its connection under format control using the insertion operator in the same way as the standard output objects (cout, cerr and clog). 

For example, the contents of the file created by the following program are shown on the right

 // Writing to a File
 // writeFile.cpp

 #include <iostream>
 #include <fstream>

 int main() {
     int i;

     std::ofstream f("output.txt");
     if (f.is_open()) {
         f << "Line 1" << std::endl;   // record 1
         f << "Line 2" << std::endl;   // record 2
         f << "Line 3" << std::endl;   // record 3
     }
 }











 Line 1
 Line 2
 Line 3 



Custom Types

Insertion and extraction operators that have been overloaded for standard output and input objects respectively as left operands and a custom compound type as the right operand work without modification with file objects as left operands.  This flexibility has to do with inheritance, which is described later in the chapter entitled Functions in a Hierarchy.  Neither the header file nor the implementation file require any modification. 

Since accepting input from a file does not involve the interaction that we expect across a standard input device, we typically overload the extraction operator to work differently with file objects.  We overload the operator for an ifstream object as the left operand.

For example, we add the prototype for the file extraction helper to the definition of our Student class:

 // Student.h

 #include <iostream> // for std::ostream, std::istream
 #include <fstream>  // for std::ifstream
 const int M = 13;

 class Student {
     int no;
     char grade[M+1];
 public:
     Student();
     Student(int);
     Student(int, const char*);
     void display(std::ostream& os) const;
     Student& operator+=(char);
     bool empty() const { return no == 0; }
     int number() const { return no; }
     const char* marks() const { return grade; }
 };

 std::istream& operator>>(std::istream& is, Student& s);
 std::ifstream& operator>>(std::ifstream& is, Student& s);
 std::ostream& operator<<(std::ostream& os, const Student& s); 
 bool operator==(const Student&, const Student&); 
 Student operator+(const Student&, char);
 Student operator+(char, const Student&);

The implementation file contains the definition of the file extraction operation for our Student class:

 // Student.cpp

 #include <cstring>
 #include "Student.h"

 Student::Student() {
     no = 0;
     grade[0] = '\0';
 }

 Student::Student(int n) {
     *this = Student(n, "");
 }

 Student::Student(int n, const char* g) {
     // see Current Object chapter for validation logic
     no = n;
     std::strcpy(grade, g);
 }

 void Student::display(std::ostream& os) const {
     os << no << ' ' << grade;
 }

 Student& Student::operator+=(char g) {
     int i = strlen(grade);
     if (i < M) {
         // add validation logic here
         grade[i++] = g;
         grade[i] = '\0';
     }
     return *this;
 }

 std::ostream& operator<<(std::ostream& os, const Student& st) { 
     st.display(os);
     return os;
 }

 std::istream& operator>>(std::istream& is, Student& s) {
     int no;
     char grade[M+1];

     // student number
     std::cout << "Number : ";
     is >> no;

     // student grades
     std::cout << "Grades : ";
     is.ignore();             // swallow newline in the buffer
     is.getline(grade, M+1);  // read string with whitespace

     Student temp(no, grade);
     if (!temp.empty())
         s = temp;  // replace s only if not empty
     return is;
 }

 std::ifstream& operator>>(std::ifstream& is, Student& s) {
     int no;
     char grade[M+1];

     // student number
     is >> no;
     is.ignore(); // skip whitespace

     // student grades
     is.getline(grade, M+1);  // read string with whitespace

     Student temp(no, grade);
     if (!temp.empty())
         s = temp;  // replace s only if not empty
     return is;
 }

Note the difference between the overloaded definitions for the extraction operator.  The ifstream definition omits the user prompts.

The client file that uses this upgraded Student class creates the file objects, writes to them and reads from them:

 // Custom File Operators
 // customFile.cpp

 #include "Student.h"

 int main ( ) {
     Student harry(975, "AABD"), josee(976, "BAAA"); 

     std::ofstream oufile("Student.txt");
     oufile << harry << std::endl;
     oufile << josee << std::endl;
     oufile.close();
     std::cout << harry << std::endl;
     std::cout << josee << std::endl;

     std::ifstream infile("Student.txt");
     infile >> harry;
     infile >> josee;
     harry += 'B';
     josee += 'C';
     std::cout << harry << std::endl;
     std::cout << josee << std::endl;
 }












 975 AABD
 976 BAAA






 975 AABDB
 976 BAAAC 

The records written to the Student.txt file by this program are:

 975 AABD
 976 BAAA

Nice To Know

Open-Mode Flags

A file object's connection mode we be customized through combinations of flags passed as the second argument to the object's constructor or its open() method. 

Six flags define the connection mode:

  • std::ios::in open for reading
  • std::ios::out open for writing
  • std::ios::app open for appending
  • std::ios::trunc open for writing, but truncate if file exists
  • std::ios::ate move to the end of the file once the file is opened

Practical combinations of these flags include

  • std::ios::in|std::ios::out open for reading and writing (default)
  • std::ios::in|std::ios::out|std::ios::trunc open for reading and overwriting
  • std::ios::in|std::ios::out|std::ios::app open for reading and appending
  • std::ios::out|std::ios::trunc open for overwriting

The vertical bar (|) stands for 'or' and is called the bit-wise or operator.

The Defaults

The default combinations are:

  • ifstream - std::ios::in open for reading
  • ofstream - std::ios::out open for writing
  • fstream - std::ios::in|std::ios::out open for reading and writing

The Logical Negation Operator

The logical negation operator (!) is overloaded in the standard input-output library as an alternative to the fail() query.  This operator reports true if the latest operation failed or if the stream has encountered a serious error. 

We can apply this operator to any stream object to check the success of the most recent action:

 if (fin.fail()) {
     std::cerr << "Read error";
     fin.clear();
 }
 if (!fin) {
     std::cerr << "Read error";
     fin.clear();
 }

The logical negation operator (!) applied to a file object returns the state of the connection:

 #include <iostream>
 #include <fstream>

 std::ofstream fout("output.txt");  // connects fout to output.txt for output 

 if (!fout) {
     std::cerr << "File is not open" << std::endl;
 } else {
     // file is open

 }

Rewinding a Connection

istream, fstream

To rewind an input stream we call:

  • istream& seekg(0) - sets the current position in the input stream to 0

ostream, fstream

To rewind an output stream we call:

  • ostream& seekp(0) - sets the current position in the output stream to 0

Closing a Connection Early

To close a file connection before the file object goes out of scope, we call the close() member function on the object: 

 // Concatenate Two Files
 // concatenate.cpp

 #include <fstream>

 int main() {
     std::ifstream in("src1.txt");    // open 1st source file
     std::ofstream out("output.txt"); // open destination file 

     while (!in.eof())
         out << in.get();        // byte by byte copy
     in.clear();
     in.close();                 // close 1st source file
     in.open("src2.txt");        // open 2nd source file

     while (!in.eof())
         out << in.get();        // byte by byte copy
     in.clear();
 }

Writing to and Reading from the Same File

A file object of the fstream class can write to a file and read from that same file. 

For example, the output for the following program is shown on the right

 // File Objects - writing and reading
 // fstream.cpp

 #include <iostream>
 #include <fstream>

 int main() {

     std::fstream f("file.txt",
      std::ios::in|std::ios::out|std::ios::trunc);
     f << "Line 1" << std::endl;   // record 1
     f << "Line 2" << std::endl;   // record 2
     f << "Line 3" << std::endl;   // record 3
     f.seekp(0);                   // rewind output
     f << "****";                  // overwrite

     char c;
     f.seekg(0);                   // rewind input
     f << std::noskipws;           // don't skip whitespace
     while (f.good()) {
         f >> c;                   // read 1 char at a time
         if (f.good())
             std::cout << c;       // display the character
     }
     f.clear();                    // clear failed (eof) state 
 }






















 **** 1
 Line 2
 Line 3 


Summary

  • the extraction and insertion operators are overloaded for file objects as left operands and fundamental types as right operands
  • an input file object is an instance of an ifstream class
  • an output file object is an instance of an ofstream class
  • the extraction and insertion operators may be overloaded for file objects as left operands and custom compound types as right operands

Exercise




Previous Reading  Previous: Custom I/O Operators Next: Derived Classes   Next Reading


  Designed by Chris Szalwinski   Copying From This Site   
Logo
Creative Commons License