Skip to content
Topos Uranos

Summary

Everything a program computes disappears when it ends, unless it leaves it written somewhere that outlives it. This lesson presents that place, the text file, and the idea that allows it to be used without learning anything radically new: a file is read and written as a stream, with the same operations as the keyboard and the screen. It studies std::ifstream and std::ofstream, the check that a file was opened before using it, and the working directory, which decides where the file is looked for; the correct reading loop, while (std::getline(in, line)), as opposed to the loop with eof(), which processes one item too many, and the exact reason for that item; writing with std::println, append mode and closing on leaving the block. The lesson ends with line formats: separating fields with commas, validating them with std::from_chars and deciding, in the specification, what to do with malformed lines.

Learning objectives

  1. Open a file for reading or for writing and check that it was opened before using it, distinguishing the file that does not exist from the one that is being looked for in another directory.
  2. Read a file line by line with while (std::getline(in, line)) and explain, by following the state of the stream, why while (!in.eof()) processes one item too many.
  3. Write results to a file with std::println(out, …), choosing between replacing its contents and appending at the end.
  4. Process a file of comma-separated values into a vector of records, validating each line and reporting the invalid ones with their number.

A file is a stream

Persistence

The variables of a program live in memory, and a program’s memory is released when it ends: the next run starts without remembering anything. For an item of data to outlive the program that produced it, it must remain on a permanent medium, and the general-purpose medium is the file: a named sequence of bytes, stored on disk, that the operating system keeps between runs. A text file is one whose bytes are characters, in UTF-8 as seen in the lesson Text is not ASCII, organized in lines separated by the character '\n'. It is the most modest format and the most durable: any editor opens it, any language reads it, and a person can inspect it at a glance.

Now, C++ does not treat a file as something different from the keyboard. Since the lesson Input, output and errors, std::cin has been an input stream: a source of characters that are consumed in order, with a state that records whether the last operation failed. A file opened for reading is another input stream, of type std::ifstream (from input file stream); a file opened for writing, an output stream, of type std::ofstream. Both are declared in the header <fstream>. It follows that everything learned about std::cin in the lesson Reading well, the operator >>, std::getline and the error state, holds unchanged for a file.

Opening and checking

A file is opened when the stream is created, with the name of the file in braces: std::ifstream in{"grades.txt"};. Opening can fail (the file does not exist, there is no permission to read it, the name is misspelled), and the failure neither stops the program nor produces any message: it leaves the stream in an error state. A stream in error, used as a condition, is false; therefore the check is if (!in), and it must be made before the first read. The following program tries to open a file that does not exist.

C++ program
#include <fstream>#include <iostream>#include <print>​int main(){    std::ifstream in{"grades.txt"};    if (!in) {        std::println(std::cerr, "Could not open grades.txt");        return 1;    }    int first{};    in >> first;    std::println("First grade: {}", first);}
Output
Could not open grades.txt

The program exited with code 1.

Three details deserve attention. The first is std::cerr (from <iostream>), the error stream: it writes to the screen like standard output, but it is a separate channel, so that error messages do not mix with the results when the output is redirected to a file; std::println accepts as its first argument the stream it must write to. The second is the return 1;: a nonzero termination code, as seen in the lesson Anatomy of a program, tells whoever ran the program that something went wrong. The third is what would have happened without the check: in >> first on a stream in error does nothing, and the program would have written “First grade: 0” with complete composure. A false result that looks true is worse than an error message; hence the rule of this lesson: every file is checked when it is opened.

Relative paths and the working directory

The name "grades.txt" does not say which directory the file is in. A name like this is a relative path, and it is resolved starting from the working directory: the directory from which the program is run, which is not necessarily the directory of the executable or that of the source code. The following program, saved in the directory project next to a file data.txt, opens that file and writes its first line; it was run twice, first from project and then from the parent directory.

Terminal
$ cd project$ g++ -std=c++23 -Wall -Wextra -Wpedantic -Wconversion -Wshadow -g -fsanitize=address,undefined -fno-sanitize-recover=undefined main.cpp -o main$ ./mainFirst line: Hello from the file$ cd ..$ ./project/mainCould not open data.txt

The executable is the same, and the file is still in its place; the only thing that changed is the working directory, and with it the place where data.txt is looked for. This is the most frequent cause of “it cannot find the file”. An editor that runs the program with a button chooses the working directory on its own (in Visual Studio Code it is usually the folder open in the editor, although it depends on the configuration of the task), so it is advisable to run from the terminal, in the directory of the data, until you know what the editor does. The opposite temptation, writing in the code an absolute path such as "/home/ana/curso/data.txt" or "C:\\Users\\Ana\\data.txt", solves the problem on a single machine and creates it on all the others: the program stops working as soon as another person runs it or the directory is moved. The recommended course is the relative path with a known working directory or, better, the name of the file as data that the user provides.