14.4. Opening and Closing Files

If we imagine a stream object as a hose, opening the stream is like attaching one end of the hose to a tap or a sprinkler, and the bytes of data moving through the stream object is like the water flowing through the hose. One end of an open stream is attached to the program, and the other is attached to a file. The console I/O objects, cin, cout, and cerr are instantiated and connected to the console, which is just a special file, before a program runs. Once created and opened (i.e., connected to a file), the file I/O objects behave just like cin and cout, which means that we already know quite a bit about how to use them.

#include <iostream>
#include <fstream>
using namespace std;
(a)
ifstream
Input File Stream. The "i" stands for input and the "f" stands for file; so, a stream object from which data may be read.
ofstream
Output File Stream. The "o" stands for output and the "f" stands for file; so, a stream object to which data may be written.
fstream
File Stream. The "f" stands for file but there is no indication of direction; so, a stream object that allows both reading and writing.
(b)
A picture illustrating how stream objects move data in and out of programs. An arrow going from a file to a program represents an 'ifstream' object, and an arrow going from a program to a file represents an 'ofstream' object. Small circle inside each arrow represent the individual bytes of data moving through the stream.
(c)
File streams. The meaning or direction of "input" and "output" is always relative to a running program. Input streams read data from a file into a program, and output streams write data from a program into a file. Stream objects treat data as a sequence or stream of bytes, so it is up to the program to group the bytes to give them meaning.
  1. C++ I/O header files. Files in a C++ program that perform I/O must include the appropriate header files. Including fstream also includes iostream, but if the program file also performs console I/O, it should also include iostream for clarity.
  2. The main file I/O stream classes. While the fstream class is more flexible - programs can both read and write files through instances of this class - it is more complicated to use, so we'll focus most of our attention on ifstream and ofstream.
  3. The relation between a program and stream objects. Opening an instance of ifstream attaches one end of the stream to an input file; opening an instance of ofstream attaches one end of the stream to an output file.
As "a file is a named collection of related information" (Silberschatz, Galvin, & Gagne), each file-open operation requires the file's name. Programmers can provide the name in various ways. In the following examples, file_name represents a file's name. In simple cases the name may be a string literal (a), but it is most often a string variable - either a C-string (b) or an instance of the string class (c). Most operating systems typically do not permit some characters in the directory or file names; the excluded characters depend on the specific system.
  1. "payments.txt"
  2. char* file_name;
  3. string file_name

File Modes

Each stream object has several behaviors that control how they treat or process the data as it moves between the program and the associated file. Each stream behavior is represented by, and is stored in the object, as a one-bit flag. Each flag is like a light switch: it's either it's on, enabling the behavior, or it's off, disabling the behavior. Together, these behaviors are the modes associated with every open file.

File modes really consist of a bit-mask or a bit-vector where each bit is one of the file's modes. (Stream objects also have flags that govern how data is formatted - these are controlled with the setf function - and flags that "remember" the current state of the stream.) C++ defines a pseudo data type called openmode that is used to represent the specific bit-masks. The behaviors are set when the file is opened, either with a constructor or with an open function call. The mode argument of the constructors and the open functions each have a default value that is based on the specific stream class and which makes it possible to call the functions with a single argument.

ifstream
ifstream (file_name, openmode mode = ios::in);
open(file_name, openmode mode = ios::in);
ofstream
ofstream (file_name, openmode mode = ios::out);
open(file_name, openmode mode = ios::out);
fstream
fstream (file_name, openmode mode = ios::in | ios::out);
open(file_name, openmode mode = ios::in | ios::out);
Prototypes for the stream constructors and open functions. The function prototypes show the default open modes for the different kinds of file stream classes.
ios::inOpen file for input
ios::outOpen file for output
ios::app Append data to the end of the output file (file-pointer repositioning commands are ignored, forcing all output to take place at the end of the file)
ios::ate Open the file at the end of the data (allows the file-pointer to be repositioning within the file)
ios::truncTruncates or discards the current contents of existing files
ios::binary Opens the file in binary mode (without this mask, the file is opened in text mode by default)
File open modes. File open modes are expressed as bit-masks that may be combined with the bitwise-OR operator to form bit-vectors that can override the default file open modes.

Opening And Closing Files

Unlike the console stream objects that are ready for use as soon as the program starts, instances of the <fstream> classes must be instantiated and associated with a file before a program is able to use them. The file stream classes (green) define a family of overloaded constructors. Some of the constructors associate the new stream object with (i.e., open) a specific file at the same time that the stream object is instantiated. But each stream class also defines a default constructor that creates a new stream object but does not associate it with or open a file. In the latter case, the stream member function open is called to access a specific file.

ifstream input(file_name);
// use file
// "input" closed by destructor
ifstream input;
while (....)
{
	input.open(file_name);
	//use file
	input.close();
}
(a)(b)
Opening a file. A file may be opened with a constructor or with the open member function. Each open file uses some system resource, so the operating system limits the number of files that a program may have open at any time. For this reason, it is important that a program closes a file when it is finished with it.
  1. In simple (and common) situations, a stream constructor builds the stream object and also opens the named file for processing (reading in this specific example). When the program is finished processing the file, it can explicitly call the close member function, but the stream classes also define destructors that safely close the file before destroying the stream object.
  2. Creating a new object always entails some small overhead. So, suppose a program iteratively processes many files in the same way. In that case, it is more efficient to create and reuse a single stream object and use it to open and close a different file during each iteration.

File Stream Constructors

Stream classes define a default constructor that creates a stream object, but the constructor does not open or associate the stream with a file. The classes also define constructors with two arguments. The first argument is the file name that the constructor opens and binds to the stream. The second argument is the file's modes, which the constructor sets when opening the file. The second argument has a default value, which means that the constructor may be called with either one or two parameters.

ifstream input;
ifstream input(file_name);
ofstream output;
ofstream output(file_name);
ofstream output(file_name, ios::app);
fstream file;
fstream file(file_name);
(a)(b)(c)
Instantiating stream objects. In each example, file_name is the name of the file to open, while input, output, and file are variable names that programmers may change as needed.
  1. Programs read or input data through ifstream objects: input >> height;. When the file is opened, it must exist or the open operation will fail.
    • Calling the default constructor creates a file input stream object but does not open a file.
    • The second constructor call creates a file input stream object and opens the named file.
  2. Programs write or output data through ofstream objects: output << height;. If the file exists and contains data, then the fate of the existing data depends on the modes parameter when the file is opened.
    • The default constructor creates an output file stream but does not open a file.
    • Creates an output file stream. If the file named by the constructor parameter exists, the absence of a modes value causes constructor call to discard the existing data (that is, the default behavior of ofstream constructor is to truncate the file on opening).
    • The ios::app flag (as the modes parameter) causes the stream object to retain any existing data and append the new data at the end of the file.
  3. fstream objects allow programs to both read from and write to files. The program may call many read or write operations without interruption, but the position pointer (Figure 1) must be reset with a "seek" operation whenever the program switches from reading to writing or from writing to reading. When opened, the file must exist or the open operation will fail.
    • Creates a file stream object that can read from and write to a file.
    • Creates a file stream object bound to the named file, but without an additional mode parameter the stream object can only read from the the file. See Random Access later in this chapter for more detail.

The open Function

The file stream constructor arguments are the same as the open member function, which suggests the following equivalences:

ifstream input(file_name);
ifstream input;
input.open(file_name);
(a)
ifstream input(file_name, ios::binary);
ifstream input;
input.open(file_name, ios::binary);
(b)
ofstream output(file_name);
ofstream output;
output.open(file_name);
(c)
ofstream output(file_name, ios::app);
ofstream output;
output.open(file_name, ios::app);
(d)
ofstream output(file_name, ios::app | ios::binary);
ofstream output;
output.open(file_name, ios::app | ios::binary);
(e)
fstream output(file_name, ios::in | ios::out);
fstream output;
output.open(file_name, ios::in | ios::out);
(f)
Opening and closing files.
  1. Opens a file in text mode for reading; the file must exist or the open fails
  2. Opens a file in binary mode for reading; the file must exist or the open fails
  3. Opens a file for writing; creates the file if doesn't exist; truncates or discards existing data if the file does exist
  4. Opens a file for writing; creates the file if doesn't exist; appends the new data at the end of the existing data if the file does exist
  5. Like (d) but opens the file in binary mode
  6. Opens a file stream object for reading and writing

Validating An Open File

Each stream object maintains a set of four one-bit flags that indicate the stream's current state or condition. File operations like opening, reading from, or writing to a file can change the stream's state. The ios class defines several member functions that report the stream's state based on the current flag settings. The stream subclasses inherit these reporting functions. In a Java program, most file operations occur inside a try-catch structure, and the program detects errors when an operation throws an exception. C++ takes a different approach requiring programmers to test a stream's state flags explicitly. When programmers open a file with a stream object, they validate the stream is ready with one of the inherited functions.

Of the four flags, the goodbit is the most inclusive. The good() function returns true only if there are no errors on the stream - indicating that a newly opened file is ready for reading or writing. If any of the error or end-of-file flags are set, good() returns false. Read and write operations can set two error flags. Programmers can test those flags with the bad() and fail() and fail functions. The next section covers the end-of-file bit, eofbit, and the eof() function. Please see the "State flag functions" for more detail.

ifstream file(file_name);

if (! file.good())
{
    cerr << "Unable to open " << file_name << endl;
    exit(1);
    //return;
}

// process the file
 
 
ifstream file(file_name);

if (file.good())
{
    // process the file
}
else
{
    cerr << "Unable to open " << file_name << endl;
    exit(1);
    //return;
}
Testing a file to insure that it opened successfully. There are several reasons that a file may not open successfully:
  1. The individual running the program may not have permissions to create or open a file in the specified directory
  2. When creating a new file, if a directory with same name already exits, the file can not be created or opened
  3. Opening a file for writing will fail if the file is "busy" (that is, open by another program)
  4. When opening a file for reading, if the named file doesn't exist, the open operation will fail
Although there are many ways to validate that a file is ready for use, the above code fragments illustrate two common patterns for testing any stream - ifstream, ofstream, or fstream.