Why should you choose fstream classes for file I/O in C++?
The <fstream> file I/O library offers several advantages over ANSI C’s <stdio.h>, including an object-oriented interface, internationalization and localization support, safety, and simpler usage. Let's take at look at how you can use this library efficiently and effectively.
Introducing <fstream> classes
The <fstream> library includes three basic classes: ifstream, ofstream, and fstream, which represent an input file, an output file, and a combined input and output file, respectively. Class ifstream supports the overloaded >> operator. Class ofstream supports the << operator. Class fstream supports both << and >> operators. All <fstream> objects may take a filename as a constructor’s argument and automatically open that file. For example:
The <fstream> classes’ destructors automatically flush the file’s content and close it, so there’s no risk of file corruption if you don’t close the file explicitly. If you don’t provide a filename during construction, you can open the file later by calling the open() member function. Here’s an example:
To demonstrate the high-level features of the <fstream> library, let’s look at a concrete example. Listing A contains a program that creates an ifstream object called dictionary, opens a file called dict.txt, and prints each word in it on the screen.
Consider how simple this program is compared to a typical <stdio.h>-based implementation. For starters, there’s no need to check for an EOF condition after each read operation. The <fstream> classes overload the ! operator. If an error has occurred, the ! operator evaluates as true. We take advantage of this feature to check whether the file was opened successfully after its construction. The while loop also uses this operator implicitly to check the file’s status on each iteration. Finally, when the program terminates, dictionary’s destructor closes the file automatically.
If you don't specify explicit open modes, fstream classes use default modes. For instance, ifstream opens a file in read mode by default and sets the file pointer at the file's beginning. Similarly, ofstream opens a file in read mode. You may combine multiple flags by using the bitwise OR operator like this:
ofstream logfile("login.dat", ios::binary|ios::app);
Listing B contains a list of the open modes and file attributes.
<fstream> versus <fstream.h>
The prestandard <fstream.h> (which is now considered deprecated and therefore should not be used in new code) supported the ios::nocreate and ios::noreplace flags. The <fstream> library doesn't support these flags anymore. However, this article shows how you can easily imitate their functionality.
A file object has a logical pointer that points to a certain offset within the physical file. You can position this pointer to an arbitrary location by calling the seekp() member function. This member function advances the file position by a specified offset from the current position. In the following example, the program advances the file's position 10 bytes and then calls tellp() to report the new position:
fout.seekp(10); // move 10 bytes ahead from beginning
cout<<"new position: "<<fout.tellp(); // display 10
The seekp() member function has another overloaded version, which takes a direction as a second argument. For instance, this function call moves the current file position two bytes back from the current position.
You can use the following constants to indicate the direction of the file’s position:
ios::beg // position at file's beginning
ios::cur //current position, for example: ios::cur+5
ios::end // position at file's end
Combine read and write operations
The <fstream> classes overload the << and >> operators for all the built-in datatypes, as well as for some of the standard classes, such as std::string and std::complex. Listing C shows how to use these operators to perform a combined read and write operation. The program opens a file, writes two fields to it, rewinds to the file’s beginning, and then reads the previously written fields into time_t and std::string objects.
One of the novel features of the <fstream> library is wchar_t support. Each of the classes I’ve discussed thus far has a wchar_t equivalent that supports files containing wchar_t data. The names of these classes are wifstream, wofstream, and wfstream. However, the C++ standard supports only char-oriented filenames, so these classes may take only char * arguments, regardless of their content. Listing D shows a whcar_t version of the program we saw in Listing A. Notice again how simple the migration from char-oriented I/O to wchar_t is.
<fstream> is a viable alternative to <stdio.h>
Admittedly, <fstream> isn’t flawless, nor does it address every need of every programmer. For example, UNIX programmers who are used to working with file descriptors rather than filenames would find <fstream> classes rather frustrating, since there’s no standard means for converting descriptors to filenames and vice versa. Similarly, the lack of support for wchar_t filenames could be problematic in certain localized environments. Still, if your aim is to use <fstream> as an alternative to <stdio.h>, you will find that it’s more intuitive and robust and often requires fewer keystrokes than its C equivalent.