Search

18.6 — Basic file I/O

File I/O in C++ works very similarly to normal I/O (with a few minor added complexities). There are 3 basic file I/O classes in C++: ifstream (derived from istream), ofstream (derived from ostream), and fstream (derived from iostream). These classes do file input, output, and input/output respectively. To use the file I/O classes, you will need to include the fstream header.

Unlike the cout, cin, cerr, and clog streams, which are already ready for use, file streams have to be explicitly set up by the programmer. However, this is extremely simple: to open a file for reading and/or writing, simply instantiate an object of the appropriate file I/O class, with the name of the file as a parameter. Then use the insertion (<<) or extraction (>>) operator to write to or read data from the file. Once you are done, there are several ways to close a file: explicitly call the close() function, or just let the file I/O variable go out of scope (the file I/O class destructor will close the file for you).

File output

To do file output in the following example, we’re going to use the ofstream class. This is extremely straighforward:

If you look in your project directory, you should see a file called Sample.dat. If you open it with a text editor, you will see that it indeed contains two lines we wrote to the file.

Note that it is also possible to use the put() function to write a single character to the file.

File input

Now, we’ll take the file we wrote in the last example and read it back in from disk. Note that ifstream returns a 0 if we’ve reached the end of the file (EOF). We’ll use this fact to determine how much to read.

This produces the result:

This
is
line
1
This
is
line
2

Hmmm, that wasn’t quite what we wanted. Remember that the extraction operator breaks on whitespace. In order to read in entire lines, we’ll have to use the getline() function.

This produces the result:

This is line 1
This is line 2

Buffered output

Output in C++ may be buffered. This means that anything that is output to a file stream may not be written to disk immediately. Instead, several output operations may be batched and handled together. This is done primarily for performance reasons. When a buffer is written to disk, this is called flushing the buffer. One way to cause the buffer to be flushed is to close the file -- the contents of the buffer will be flushed to disk, and then the file will be closed.

Buffering is usually not a problem, but in certain circumstance it can cause complications for the unwary. The main culprit in this case is when there is data in the buffer, and then program terminates immediately (either by crashing, or by calling exit()). In these cases, the destructors for the file stream classes are not executed, which means the files are never closed, which means the buffers are never flushed. In this case, the data in the buffer is not written to disk, and is lost forever. This is why it is always a good idea to explicitly close any open files before calling exit().

It is possible to flush the buffer manually using the ostream::flush() function or sending std::flush to the output stream. Either of these methods can be useful to ensure the contents of the buffer are written to disk immediately, just in case the program crashes.

One interesting note is that std::endl; also flushes the output stream. Consequently, overuse of std::endl (causing unnecessary buffer flushes) can have performance impacts when doing buffered I/O where flushes are expensive (such as writing to a file). For this reason, performance conscious programmers will often use ‘\n’ instead of std::endl to insert a newline into the output stream, to avoid unnecessary flushing of the buffer.

File modes

What happens if we try to write to a file that already exists? Running the output example again shows that the original file is completely overwritten each time the program is run. What if, instead, we wanted to append some more data to the end of the file? It turns out that the file stream constructors take an optional second parameter that allows you to specify information about how the file should be opened. This parameter is called mode, and the valid flags that it accepts live in the Ios class.

Ios file mode Meaning
app Opens the file in append mode
ate Seeks to the end of the file before reading/writing
binary Opens the file in binary mode (instead of text mode)
in Opens the file in read mode (default for ifstream)
out Opens the file in write mode (default for ofstream)
trunc Erases the file if it already exists

It is possible to specify multiple flags by bitwise ORing them together (using the | operator). Ifstream defaults to std::ios::in file mode. Ofstream defaults to std::ios::out file mode. And fstream defaults to std::ios::in | std::ios::out file mode, meaning you can both read and write by default.

Let’s write a program that appends two more lines to the Sample.dat file we previously created:

Now if we take a look at Sample.dat (using one of the above sample programs that prints its contents, or loading it in a text editor), we will see the following:

This is line 1
This is line 2
This is line 3
This is line 4

Explicitly opening files using open()

Just like it is possible to explicitly close a file stream using close(), it’s also possible to explicitly open a file stream using open(). open() works just like the file stream constructors -- it takes a file name and an optional file mode.

For example:


18.7 -- Random file I/O
Index
18.5 -- Stream states and input validation

152 comments to 18.6 — Basic file I/O

  • masterOfNothing

    Hey, has anyone ever experienced std::ofstream not being able to create and/or open a simple text file (or any file for that matter). Suddenly code::blocks fails to perform std::ofstream operation. But std::ifstream on the same file works well.

    Like in this simple program (the file "test.txt" contains a word 'Something'):

    //Opening a file with std::ofstream:
    //Something didn't work
    //Goodbit? - Not good
    //Logical error on I/O operation? - yes
    //
    //Opening the same file with std::ifstream:
    //File is open
    //Contents of the .txt file: "Something"

    The file is located in the current project's directory. I don't know why std::ofstream behaves like that suddenly. And it takes a pause before continuing with the program. Searches for the file? Maybe something is wrong with how code::block looks for current working directory? I haven't meddled with the setting of the software. I remember some time ago doing some file handling tutorials and it worked fine.

    Neither relative nor absolute path works. std::ofstream cannot even create a new .txt file if it doesn't exists in the directory.

    • nascardriver

      Print an error message to see what's failing

      I don't see anything wrong with your code. Delete the file, create it, don't open it anywhere else, run your program.

      • masterOfNothing

        Hey, nascardriver!

        Thank you for the reply. And also for the error reading trick. Learned something new :)

        Back to business. It appears there is some sort of 'permission denied' error. Strange, because I run the code::blocks as administrator on the OS. So there should really be no problems in that area. Maybe the compiler needs some sort of permission as well? But this has never happened before. Should I reinstall code::blocks?

        Here's the error output on the console:

        // Something didn't work: Permission denied

        It couldn't be anti-virus blocking creating/writing to the file?

        *notices Avast anti-virus is on silent mode, turns off silent mode, compiles again. Avast complains!*
        Yes, indeed! The avast was blocking fstream.exe.
        What a relief! I believe the last avast update changed something.
        Anyway, thanks for the help. Knowing the nature of the error partially allowed me to deduce the problem! :)

        After fixing the issue. The program runs:

        // Opening a file with std::ofstream:
        // File is open
        // Goodbit? - All good!
        // Logical error on I/O operation? - no
        //
        // Opening the same file with std::ifstream:
        // File is open
        // Contents of the .txt file: "Something"

  • Avdoot

    I am trying to open and read from a file using the following code:-

    The code compiles perfectly. But, I get the message "Cannot open the file. Please check if the file exist!\n". The files data.txt and main.cpp reside in the same folder named "src" and the executable is in a folder named "bin". Could you please tell me my mistake?

    I am using Ubuntu 18.04 and Eclipse IDE.

    Thank You!

    • nascardriver

      If this is your directory structure, and you run "executable" from within "bin", your code should work. I suppose you're running "executable" from somewhere else. In your project settings or launch configuration, you should be able to change the working directory.

  • Al

    On GCC

    won't compile. Isn't the following the proper namespace resolution?

    The same question holds for any other fstream mode.

  • Viktar

    Dear comrades,

    Could you help me to identify the issue please ?

    I've got

    Then a next class

    The problem comes when I try to load the file.

    I tried to debug this(temporary added Worker local variable and use it with inf.read) and noticed that while loadFromFile process Worker object gets an error "<error: Cannot access memory at address 0x5573feda7810>" exactly for std::string variables.

    I replaced for test std::string to char[64] and it works fine.

    So looks like there's some I don't know. I tried to use reinterpret_cast. And unfortunately not luck.
    I suspect std::ios::binary works the only with fundamental types... but not sure. Could you help me and explain a little is it possible to use std::ios::binary with the classes where std::string is presented as class member ?

    Thank you.

    • nascardriver

      `Worker` is not a trivial type. To important member is `std::string`. It doesn't store the string directly, but stores a pointer to a C-style string. When you write an `std::string` to a file byte-by-byte, you'll write the pointer. When you load it from a file, the same thing happens in reverse. This pointer is not valid when you load it from a file. You're never saving the actual string. Similar to a shallow copy, you're not saving the data, but only the pointer.

      Rather than dumping the memory to a file, I recommend writing to the file in a structured manner, so that you know exactly where which variable is stored, and can load it later on.

      • Viktar

        Thank you nascardriver.

        And if there some best practice of structured manner to write to the file ? Some approach?

        And thank you again.

        • nascardriver

          You can create an enum with types (int, double, string). Then you can structure your file as

          Where `type` has a fixed size, eg. 1 byte. You read the first byte, it says that the data is an `int`, so you read and `int` with a fixed size (eg. 4 bytes). Now you're done with the first entry and you know where the next type entry is. You read that and maybe it's a string. There are several ways of storing strings. The easiest being as zero-terminated strings. You know that the data is a string, so you can read until you find a 0. Then you look at the next type entry and so on. Alternatively to zero-terminated strings, you can store the length separately from the string, eg. the first 4 bytes of string data are the string's length, followed by the actual string.

          Some sample data

          where 0=int, 1=double, 2=string

          An easier, but less safe way is converting everything into a trivial type. So, add another `struct` with your ints and doubles, those are fine, but instead of using a `std::string`, you use a fixed-size C-style string.

Leave a Comment

Put all code inside code tags: [code]your code here[/code]