C++ - C++ read file - Programming Languages

Our Guide to C++ Files

Working with files is a vital skill within the arsenal of a C++ developer. From reading configuration files to writing your application’s output to disk, different types of file operations are essential for many basic and advanced programs.

We’ve created this guide to give you a quick glance into the most frequently used file reading and writing operations. Let’s dive in!

Reasons To Use Files in C++

When working in the C++ programming language, you might find yourself needing to work with files for one of these three reasons:

1) Interface with other programs. By writing your files in a standard format (e.g., CSV), you can ensure that your program’s output is capable of being read by other programs.

2) Store outputs for future use. If you want to preserve information between executions of your program, placing files on the local filesystems is the easiest (and frequently the only) option. 

3) Free up memory during program execution. If you’re working with a large data set, chances are it will not entirely fit in memory. Using the filesystem to store chunks of the data you’re not immediately using is a great solution for handling large volumes of data.

There are other use cases for files, too. But for now, let’s look at how files work in C++.

File Streams in C++

File streams are the native way of reading from and writing to files in C++. If you’re familiar with C++ input and output streams, using file streams will feel familiar.

The input/output stream header <iostream> implements formatted input/output for a generic stream, whereas the file stream header <fstream> implements this formatted input/output specifically on file streams. Three file stream classes are part of the <fstream> header:

  • fstream (generic file stream — for input and/or output)
  • ifstream (input file stream — for reading files)
  • ofstream (output file stream — for writing files)

These three classes are functionally equivalent but differ in their default settings. For example, an ifstream object defaults to being an input-only stream but can be configured to enable output.

Let’s see these file stream classes in action.

C++ Example: Opening a File for Reading

To open a file for reading, we’ll need to create a new file stream and then point it to the file that we want to read:

#include <fstream>
#include <iostream>
using namespace std;

int main() {
  ifstream fs("the_raid.txt");
  // alternatively:
  // ifstream fs;
  // fs.open("the_raid.txt");
  if (fs.is_open()) {
    cout << fs.rdbuf();
  }
  fs.close();
}

After reading the stream, we use the fs.is_open() function to check if the stream has been correctly opened. We then use the fs.rdbuf() function to access the stream’s read buffer, the memory location where the stream implementation stores the contents of the file that it just read. Inspecting the read buffer is a quick way to get the stream’s contents.

Let’s compile the program and observe its output:

$ g++ -o files.x files.cpp
$ ./files.x
On the twelfth of July, Captain Hlopov entered the low door of my earth-hut. He was wearing epaulettes and carrying a sword, which I had never seen him do before since I had reached the Caucasus.

Great! We’ve successfully read the file. Now, let’s try reading the file line-by-line.

C++ Example: Reading a File Line-by-line

We won’t typically need to read an entire file at once, but rather read and process it line-by-line. With C++ file streams, we can use the standard getline() function to read a line from a file stream into a string:

#include <fstream>
#include <iostream>
#include <string>
using namespace std;

int main() {
  ifstream fs("the_raid.txt");
  string input_line;
  while (getline(fs, input_line)) {
    cout << input_line << endl << endl;
  }
}

The program will run through the entire file (until getline() is false) and print it out line-by-line:

$ ./files.x
On the twelfth of July, Captain Hlopov entered the low door of my earth-hut. He was wearing epaulets and carrying a sword, which I had never seen him do before since I had reached the Caucasus.
“I come straight from the colonel’s,” he said in answer to my questioning look. “Tomorrow, our battalion is to march.”
“Where to?” I asked.
“To N. N. The forces are to assemble there.”

You can observe the double line breaks appearing in the output between individual lines of the dialog in our text.

Now, let’s try writing to a file.

C++ Example: Write to File

To write into a file, we’ll switch to using an ofstream object to indicate that we’ll use the object for output.

Let’s try adding a couple of lines to a copy of our text file. We’ll open it and will then use the standard << operator to write a string into the file stream:

#include <fstream>
#include <iostream>
using namespace std;

int main() {
  ofstream fs("the_raid_copy.txt", fs.binary);
  fs << ""And from there, I suppose, they will go into action?"" << endl;;
  fs << ""I expect so."" << endl;
}

And now let’s print out the contents of our file:

$ cat the_raid_copy.txt
"And from there, I suppose, they will go into action?"
"I expect so."

Snap! Instead of appending the dialog to the end of the file, we’ve overwritten its contents. If we wanted to append to a file, we would have needed to indicate that upon creating the stream by using the fs.app option. Let’s modify our example to append to the file:

...
  ofstream fs("the_raid_copy.txt", fs.app | fs.binary);
...

Note the new option field with the app and binary options. app stands for “append,” so the new writes will now be appended to the end of the file. The binary option tells the C++ runtime to not translate our line ending when writing to the file — more on that later. The options are combined through the | —  also known as “or”– operator.

Let’s try running this modified program with a fresh copy of our text file:

$ cat the_raid_copy.txt
On the twelfth of July, Captain Hlopov entered the low door of my earth-hut. He was wearing epaulettes and carrying a sword, which I had never seen him do before since I had reached the Caucasus.
"I come straight from the colonel's," he said in answer to my questioning look. "Tomorrow our battalion is to march."
"Where to?" I asked.
"To N. N. The forces are to assemble there."
"And from there, I suppose, they will go into action?"
"I expect so."

We’ve now appended our dialog to the end of the file rather than overwriting it.

To Close or Not To Close?

In our examples, we opened file streams but did not explicitly close them. If you’re coming from a language like C, this behavior might worry you.

In C++, fstream, ifstream and ofstream objects (among others) are all RAII objects. Such objects are created on the program’s stack space in memory. An important — and convenient — feature of such objects is that they’re automatically cleaned up by the C++ runtime whenever they go out of scope.

Specifically, if file stream object fs is defined and used within a function or a statement, C++ will automatically destroy the object when this section of the program finishes running. Upon destruction, fs.close() will be called on a file stream implicitly.

What does this mean for a C++ developer? Files are now closed automatically! (With a caveat, of course.) See the following example:

#include <fstream>
#include <iostream>
using namespace std;

int main() {
  ofstream fs("the_raid_copy.txt", fs.binary);
  fs << ""And from there, I suppose, they will go into action?"" << endl;;
  fs << ""I expect so."" << endl;
  // fs.close(); <-- implicit!
}

Here, we didn’t need to call fs.close() at the end of the function: The cleanup function gets called by the C++ runtime if we don’t call it manually.

The caveat is that explicitly closing the file allows us to check for any errors in the process, which might be a good idea for us to perform. Calling the close() function can cause the contents of the stream’s write buffer to be committed to disk, provided there’s anything in the buffer. If there’s an issue with the disk at the time of writing, such as if the USB disk has been unplugged, the close() function will fail, and the contents might not get written to the storage medium.

While in a normal use case, the probability of close() failing is low, it’s a good idea to close the file manually and check for errors if you’re saving valuable data to disk.

A failure during the close() operation will set the failbit value on the stream, and here’s how we can check for it:

#include <fstream>
#include <iostream>
using namespace std;

int main() {
  ofstream fs("the_raid_copy.txt", fs.binary);
  fs << ""And from there, I suppose, they will go into action?"" << endl;;
  fs << ""I expect so."" << endl;
  fs.close();
  if (fs.fail()) {
    cout << "Something bad happened!" << endl;
  }
}

Another reason why we would want to explicitly close a stream is to ensure that there aren’t any race conditions between various streams operating on the same file. Because data can be held in a stream’s write buffer, there can be a delay between executing the write-to-the-stream command and updated information on the disk.

If one stream is writing a file while another stream is trying to read its contents, the reading stream might end up reading outdated content. Closing a file stream when you’re finished operating on it, thus causing changes to get saved to disk, can help reduce the incidence of race conditions.

Note: The close() function will not ensure that the stream’s contents make it to the disk, but will ensure that the write instructions get submitted to the operating system. It falls upon the operating system to ensure that the data gets written to disk. 

Text and Binary Modes in C++ File Streams

Above, we’ve worked in binary mode, which was set through our fs.binary flag. The binary mode is one of the two modes for working with file streams in C++. The other, text mode, is the default mode that’s used in the absence of a binary flag.

The difference between the two modes lies in the translation of line endings. When we use a stream in text mode, the C++ runtime will take care of translating the line endings (e.g., \n on Linux and \n\r on Windows) for our platform. In text mode, the developer does not need to check for a particular platform and can just use the \n character (or the endl shorthand), and the runtime will adapt for whatever system the program may be running on.

With binary mode, by contrast, the developer becomes responsible for managing the line endings manually. Binary mode assumes that the files being worked on are not text, and that introducing any translation might cause file formatting issues.

So when working with text files, we recommend using the default text mode for ease of use. And if you’re reading and writing binary files, definitely use binary mode to avoid any formatting issues due to the newline translation.

Become a C++ Developer

If you’d like to dive deeper and learn how to build a C++ application that relies on reading and writing files, consider enrolling in our specialized C++ Nanodegree program.

You’ll master concepts like object-oriented programming and memory management, and will be ready to use C++ for exciting tasks like self-driving cars and robotics, web browsers, media platforms, and even video games.

Enroll in Udacity’s C++ Nanodegree today!

Start Learning