std::filesystem
library.In this lesson, we’ll explore the standard library functionality that helps us work with file systems in a platform-agnostic way.
The file system functionality is available by including <filesystem>
#include <filesystem>
Most of the functions and types we’ll be using are within the std::filesystem
namespace, which we’ll alias to fs
to keep our code less verbose:
namespace fs = std::filesystem;
A directory_entry
is the main type we use to refer to objects within our file system, such as files and directories. We can initialize a directory_entry
by passing the path to it on our hard drive.
#include <filesystem>
namespace fs = std::filesystem;
int main() {
fs::directory_entry Directory{R"(c:/test)"};
}
Given paths often include backslashes, which denote escape sequences in strings, we need to give them extra consideration. When we want a string to contain a literal \
, we need to escape it with an additional \
, so the string representing a path like c:\test
would use the string "c:\\test"
Alternatively, we can use raw strings, which allow us to provide our path as-is:
#include <filesystem>
namespace fs = std::filesystem;
int main() {
// Using a regular string
fs::directory_entry A{"c:\\test"};
// Using a raw string
fs::directory_entry B{R"(c:\test)"};
}
The raw string approach tends to be more readable, so it’s what we’ll use in the rest of this lesson.
Directory entries have a lot of useful methods we can use to investigate the nature of what our path is pointing at.
For example, we can check if an entry exists at the path by using the exists()
method. We can also check if it is a directory or file by using the is_directory()
and is_regular_file()
 methods.
In the following example, c:\test
is a directory on our hard drive:
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::directory_entry Directory{R"(c:\test)"};
if (Directory.exists()) {
std::cout << "The location exists";
}
if (Directory.is_directory()) {
std::cout << "\nIt is a directory";
}
if (!Directory.is_regular_file()) {
std::cout << "\nIt is not a file";
}
}
The location exists
It is a directory
It is not a file
Almost all of the files we’re familiar with on our hard drives - documents, images, executables, and more, are considered regular files.
Beyond directories and regular files, several other things can exist within our file systems, such as block files and symbolic links. For this lesson, we’ll just focus on directories and regular files.
file_size()
When our directory_entry
is pointing at a file, we can use the file_size
method to return its size in bytes:
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::directory_entry File{
R"(c:\test\hello.txt)"};
std::cout << "File Size: " << File.file_size()
<< " bytes";
}
File Size: 24 bytes
last_write_time()
We can find the time a file or directory was last modified by using the last_write_time()
method on our directory_entry
:
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::directory_entry File{
R"(c:\test\hello.txt)"};
std::cout << "Last Write Time:\n"
<< File.last_write_time();
}
Last Write Time:
2023-06-10 23:10:33.4202407
We can create a directory using the create_directory()
function, passing a path to the directory we want to create.
#include <filesystem>
namespace fs = std::filesystem;
int main() {
fs::create_directory(R"(c:\test)");
}
The function will return true
if the directory was created.
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
if (fs::create_directory(R"(c:\test)")) {
std::cout << "Directory created";
} else {
std::cout << "Directory not created";
}
}
Directory not created
There are two possible reasons the directory will not be created:
We’ll cover errors in more detail later in this lesson.
Sometimes, we’ll want to create a directory structure that is multiple levels deep. For example, we may want to create c:\test\subdirectory
, when c:\test
does not yet exist.
Using the create_directories
method, we can specify the exact directory we want, and if any intermediate directories are missing, they will be created too:
#include <filesystem>
namespace fs = std::filesystem;
int main() {
fs::create_directories(
R"(c:\deeply\nested\directory)");
}
Many of the functions in this lesson can throw exceptions. We can catch these in the normal way, using a try-catch block. The type of exception thrown by file system errors is std::filesystem::filesystem_error
.
Like any standard library exception, they have a what()
method that we can use to get a description of the error.
For more programmatic error handling, we will likely want to use the code()
method instead. This returns a std::error_code
object, that is much easier to work with programmatically.
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
try {
fs::create_directory(R"(k:\fake)");
} catch (fs::filesystem_error e) {
std::cout << e.code() << '\n';
std::cout << e.what();
}
}
system:3
create_directory: The system cannot find the path specified.: "k:\fake"
The meaning of error codes depends on the operating system our program is running on. The previous example is from Windows, whose error codes are available on the official site.
We can access the integer error code using the value()
 method:
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
try {
fs::create_directory(R"(k:\fake)");
} catch (fs::filesystem_error& e) {
if (e.code().value() == 3) {
std::cout << "Path doesn't exist";
}
}
}
Path doesn't exist
There are two main functions we use for copying files and directories - copy_file()
and copy()
copy_file()
We can copy files using the copy_file
method, with two arguments. The first argument is the path to the file we want to copy. The second argument is the path we want to copy it to:
#include <filesystem>
namespace fs = std::filesystem;
int main() {
fs::copy_file(R"(c:\test\hello.txt)",
R"(c:\test\hi.txt)");
}
We can pass an additional third argument to copy_file()
, which allows us to define how our code should behave if the path specified by the second argument already exists.
In such a scenario, our copy_file()
call risks overwriting a file.
The std::filesystem::copy_options
enumeration contains our options for handling this. They are:
none
- keep the existing file and throw an exception (default)skip_existing
- keep the existing file but do not throw an exceptionoverwrite_existing
- overwrite the existing fileupdate_existing
- overwrite the existing file if it’s older than the new file, as defined by the last_write_time()
#include <filesystem>
namespace fs = std::filesystem;
int main() {
fs::copy_file(
R"(c:\test\hello.txt)",
R"(c:\test\hi.txt)",
fs::copy_options::skip_existing);
}
The std::filesystem
library isn’t designed for directly creating new files. Later in this chapter, we introduce file streams, which are the typical mechanisms we use for creating and manipulating files.
copy()
We can copy a directory with the copy()
 function.
Similar to copy_file()
, we can pass options to define how the copy()
action works. When dealing with overwriting existing files, we have the same 4 options we had with copy_file()
.
We can also pass up to two additional options that take effect when what we’re copying is a directory:
recursive
- Recursively copy subdirectoriesdirectories_only
- Only copy the directory structure - ignore filesBoth options are disabled by default, but we can enable them as needed, and we can enable multiple options by combining them with the |
 operator:
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::copy(
R"(c:\test)", R"(c:\test2)",
fs::copy_options::skip_existing |
fs::copy_options::recursive |
fs::copy_options::directories_only);
}
The |
syntax in the previous example is a bitwise operator. This was covered this in more detail in our earlier course:
Additional copy()
options are available when we’re dealing with things like symbolic links, but that’s out of scope for now.
Note, that the copy()
function can copy a file - it is not restricted to just copying a directory. However, if we're copying a file, the copy_file()
method makes our intent much clearer.
Additionally, copy_file()
has built-in error-checking to ensure the target really is a file.
There are two functions we can use to delete files and directories: remove()
and remove_all()
remove()
We can remove a file or directory using the remove()
 function:
#include <filesystem>
namespace fs = std::filesystem;
int main() {
fs::remove(R"(c:\test)");
}
When the path we provide points to a directory that is not empty, an exception is thrown
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
try {
fs::remove(R"(c:\test)");
} catch (fs::filesystem_error& e) {
std::cout << "Code: " << e.code()
<< "\nError: " << e.what();
}
}
Code: system:145
Error: remove: The directory is not empty.: "c:/test"
remove_all()
When we’re trying to remove a directory, and we want to get rid of everything inside it too, we can use the remove_all()
method. It returns an integer, representing how many files and directories were removed.
The specific type returned is uintmax_t
, a fixed-width integer that can be used like any other integer type.
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
uintmax_t FilesDeleted{
fs::remove_all(R"(c:\test)")};
std::cout << "Deleted " << FilesDeleted
<< " files or directories";
}
Deleted 2 files or directories
We can move or rename a file or directory using the rename()
function. The first argument is the path to the item we want to move. The second argument will be the location we want to move it to.
#include <filesystem>
namespace fs = std::filesystem;
int main() {
fs::rename(R"(c:\test)", R"(c:\test2)");
}
The space()
function lets us get more information about the storage space associated with a path. It returns a space_info
struct, which has three fields:
capacity
- The total capacity available at the locationfree
- The amount of space free at the locationavailable
- The amount of space that is available for our program to write to. This will be less than or equal to the free
spaceAll three values are in bytes:
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
auto [capacity, free, available]{
fs::space(R"(c:\)")};
constexpr int bytesInGB{1024 * 1024 * 1024};
std::cout << "Capacity: "
<< capacity / bytesInGB << "GB"
<< "\nFree: " << free / bytesInGB
<< "GB\nAvailable: "
<< available / bytesInGB << "GB";
}
Capacity: 437GB
Free: 277GB
Available: 277GB
This lesson provided an overview of the C++ filesystem
library to effectively manage files and directories on the user’s computer. The key topics we covered included:
<filesystem>
library.directory_entry
to interact with file system objects.Create, delete, move, and navigate through directories and files using the standard library's filesystem
module.
Comprehensive course covering advanced concepts, and how to use them on large-scale projects.