File Handling and Filesystem

Working with Paths and Directories

A path object can be examined piece by piece, and one or more path objects can be used to generate a whole hierarchy of directories. The member functions that break down a path and the free functions that create and delete directories give a program fine grained control of the layout of files on which it depends.

The process's current working directory can be obtained with std::filesystem::current_path.

#include <iostream>
#include <filesystem>

int main()
{
    std::filesystem::path here = std::filesystem::current_path();
    std::cout << here.string() << '\n';
    return 0;
}

The free function std::filesystem::current_path returns a std::filesystem::path naming the directory from which the program is launched. The resulting object is placed into here. Its string member returns the path as an std::string in the platform's native format, and the insertion prints the string. All relative paths that the program subsequently forms will resolve relative to this directory, unless an absolute path is specified.

Any one of the components of a multi component path can be obtained by calling members on a path object. filename returns the last component of a path, stem returns that component without its extension, and extension returns only the extension, which starts with a period.

#include <iostream>
#include <filesystem>

int main()
{
    std::filesystem::path p{"output/logs/app.txt"};
    std::cout << p.filename().string() << '\n';
    std::cout << p.stem().string() << '\n';
    std::cout << p.extension().string() << '\n';
    return 0;
}

The path p is constructed from the string output/logs/app.txt. A call to p.filename() produces a new std::filesystem::path containing only the name app.txt, which is converted to an std::string by string and printed first. A call to p.stem() produces a path containing only app, which is then printed.

A call to p.extension() produces a path containing only .txt, printed last.

Each of these methods returns an std::filesystem::path, rather than a string, and subsequent path operations can be invoked on the returned path if desired.

The parent of a path can be obtained using parent_path.

#include <iostream>
#include <filesystem>

int main()
{
    std::filesystem::path p{"output/logs/app.txt"};
    std::filesystem::path parent = p.parent_path();
    std::cout << parent.string() << '\n';
    return 0;
}

The parent_path method returns an std::filesystem::path that contains everything but the last component of the given path. Calling this method on the path stored in p yields an std::filesystem::path object naming the directory output/logs, and this object is copied into parent, and then printed. Calling parent_path on a single component path results in an empty path.

Any number of nested directories can be created with a single call using the plural std::filesystem::create_directories. If a path is created using std::filesystem::create_directory and any intermediate directory doesn't exist, it won't work, but the plural version will create whatever intermediate paths are necessary.

#include <iostream>
#include <filesystem>

int main()
{
    std::filesystem::path dir{"output/logs/2026"};

    // Check this condition before entering the block
    if (std::filesystem::create_directories(dir))
        std::cout << "created\n";
    else
        std::cout << "not created\n";

    return 0;
}

The path dir represents a path that is three levels deep. A call to std::filesystem::create_directories tries to create output, then it tries to create output/logs, and then output/logs/2026. It returns true and prints the first message if each of the required directories did not previously exist and is successfully created.

It returns false and prints the second message if the hierarchy already exists.

Creating the hierarchy therefore won't fail if a part of it has already been constructed.

A path that represents a relative file or directory can be converted to an absolute path by calling std::filesystem::absolute.

#include <iostream>
#include <filesystem>

int main()
{
    std::filesystem::path rel{"logs/app.txt"};
    std::filesystem::path abs = std::filesystem::absolute(rel);
    std::cout << abs.string() << '\n';
    return 0;
}

The path rel is initialized to contain the relative path name logs/app.txt. A call to std::filesystem::absolute resolves this name relative to the current working directory and returns an std::filesystem::path representing an absolute path starting from the root of the filesystem. That path is then copied into abs and printed. The string printed will vary greatly depending on the location of the program, but it will always be an absolute path.

Existing paths can be compared for equality or concatenated using the division operator shown earlier. If a directory that is not empty needs to be deleted, the free function std::filesystem::remove_all should be used.

#include <iostream>
#include <filesystem>

int main()
{
    std::filesystem::path dir{"output"};
    // Remove this filesystem entry
    std::uintmax_t count = std::filesystem::remove_all(dir);
    std::cout << count << '\n';
    return 0;
}

Calling std::filesystem::remove_all(dir) will delete the directory named by dir along with everything it contains. The free function returns the number of objects that are deleted, which can then be stored in count and printed. If the path specifies something that does not exist, zero is returned. This is a very convenient way to remove an entire tree created by the program itself.

0 comments

No comments yet. Be the first.

Add a comment

About CppLearn

CppLearn is an interactive, free reference for modern C++. In each lesson, you'll find out how the language works, rather than just lines of code that you can copy and paste. We cover concepts starting from beginner lessons on fundamentals and proceed to templates, move semantics, smart pointers, and areas that usually trap or scare C++ learners.