Last Update:
std:filesystem::file_size Advantages and Differences
Table of Contents
Subtitle: Learning std::filesystem
through file_size
routines.
Last week I wrote a short post that explained how to use
std::filesystem::file_size
. Today I’d like to continue and show some
significant differences that this new functionality has over the “older”
techniques (like reading a file and getting its file position).
We’ll also learn something about permissions and how to manage them in
std::filesystem
.
Recap
STL before C++17 didn’t contain any direct facilities to work with a filesystem. We could only use third party libraries (like Boost), or system APIs.
With C++17 we have two methods:
std::uintmax_t std::filesystem::file_size(const std::filesystem::path& p);
std::uintmax_t std::filesystem::directory_entry::file_size() const;
For example, here’s a code that returns the file size:
try
{
const auto fsize = std::filesystem::file_size("test.file");
// use fsize...
} catch(fs::filesystem_error& ex)
{
std::cout << ex.what() << '\n';
}
What are the advantages (besides shorter code) over the existing C++ methods? Is this method faster?
The Series
This article is part of my series about C++17 Library Utilities. Here’s the list of the topics in the series:
- Refactoring with
std::optional
- Using
std::optional
- Error handling and
std::optional
- About
std::variant
- About
std::any
std::string_view
Performance and followup- C++17 string searchers and followup
- Conversion utilities - about from_chars.
- Working with
std::filesystem
-file_size
Resources about C++17 STL:
- C++17 In Detail by Bartek!
- C++17 - The Complete Guide by Nicolai Josuttis
- C++ Fundamentals Including C++ 17 by Kate Gregory
- C++17 STL Cookbook by Jacek Galowicz
File Permissions
The other, popular technique that is available in C++ (apart from using
third-party API) is to open a file and then read its file position (with
tellg()
). The first question we may ask is - how about file
permission? What
if you cannot open a file?
The C++17’s way doesn’t have to open a file, as it reads only file
attributes:
From
cppreference:
For a regular file p, returns the size determined as if by reading the st_size member of the structure obtained by POSIX stat (symlinks are followed)
We can check this with a simple code:
Let’s create a simple file:
std::ofstream sample("hello.txt");
sample << "Hello World!\n";
We can read the current file permissions and show them.
// adapted from https://en.cppreference.com/w/cpp/filesystem/permissions
void outputPerms(fs::perms p, std::string_view title)
{
if (!title.empty())
std::cout << title << ": ";
std::cout << "owner: "
<< ((p & fs::perms::owner_read) != fs::perms::none ? "r" : "-")
<< ((p & fs::perms::owner_write) != fs::perms::none ? "w" : "-")
<< ((p & fs::perms::owner_exec) != fs::perms::none ? "x" : "-");
std::cout << " group: "
<< ((p & fs::perms::group_read) != fs::perms::none ? "r" : "-")
<< ((p & fs::perms::group_write) != fs::perms::none ? "w" : "-")
<< ((p & fs::perms::group_exec) != fs::perms::none ? "x" : "-");
std::cout << " others: "
<< ((p & fs::perms::others_read) != fs::perms::none ? "r" : "-")
<< ((p & fs::perms::others_write) != fs::perms::none ? "w" : "-")
<< ((p & fs::perms::others_exec) != fs::perms::none ? "x" : "-")
<< '\n';
}
For our file we can see:
outputPerms(fs::status("hello.txt").permissions());
And we’ll get (On Linux at Coliru):
owner: rw- group: r-- others: r--
We have the right, so tellg()
will work as expected:
std::ifstream testFile(std::string("hello.txt"),
std::ios::binary | std::ios::ate);
if (testFile.good())
std::cout << "tellgSize: " << testFile.tellg() << '\n';
else
throw std::runtime_error("cannot read file...");
But how about changing permissions so that we cannot open the file for reading, writing or executing?
fs::permissions(sTempName, fs::perms::owner_all,
fs::perm_options::remove);
outputPerms(fs::status(sTempName).permissions());
it shows:
owner: --- group: r-- others: r--
fs::permissions
is a method that allows us to set permissions - we
pass a flag that we’d like to change (it’s a bitmask) and then
“operation” - remove
, replace
or add
.
In our case, I’m removing all owner permissions from the file.
perms::owner_all
is composed of
owner_read | owner_write | owner_exec
.
Now… let’s try to execute the same code that uses tellg()
… and we’ll
get:
general exception: cannot read file...
But how about fs::file_size
?:
auto fsize = fs::file_size(sTempName);
std::cout << "fsize: " << fsize << '\n';
We’ll get the expected output:
fsize: 13
No matter the permissions of the file (common permissions like read/write/exec), we can read its size.
Demo here @Coliru
Parent Directory Access
While there’s no need to have read/write/exec rights for the file, we need a parent directory rights to be correct.
I did one more test and I removed all rights from "."
directory (the
place were the file was created):
fs::permissions(".", fs::perms::owner_all,
fs::perm_options::remove);
auto fsize = fs::file_size(sTempName);
std::cout << "fsize: " << fsize << '\n';
But I only got:
filesystem error! filesystem error:
cannot get file size: Permission denied [hello.txt]
You can play with the code here @Coliru
Note For Windows
Windows is not a POSIX system, so it also doesn’t map POSIX file
permissions to its scheme. For std::filesystem
it only supports two
modes:
- (read/write) - read, write, and execute - all modes
- (read-only) - read, execute - all modes
That’s why our demo code won’t work. Disabling read access for a file does not affect.
Performance
Getting a file size is maybe not the crucial hot-spot in your application… but since we’re C++ programmers, we’d like to know what’s faster… right? :)
Since there’s no need to read the file… then std::filesystem
methods
should be faster… isn’t it?
What’s more, directory_entry
method might be even faster as it should
be able to cache the results for a single path - so if you want to
access that information many times, it’s wiser to use directory_entry
.
Here’s a simple test (thanks to Patrice Roy for the initial test example)
you can play with a demo code here @Coliru.
The test is run N = 10'000
times.
On Coliru (Linux):
filesystem::file_size : 2543920 in 21 ms.
homemade file_size : 2543920 in 66 ms.
directory_entry file_size : 2543920 in 13 ms.
On Windows:
PS .\Test.exe
filesystem::file_size : 1200128 in 81 ms.
homemade file_size : 1200128 in 395 ms.
directory_entry file_size : 1200128 in 0 ms.
PS .\Test.exe
filesystem::file_size : 1200128 in 81 ms.
homemade file_size : 1200128 in 390 ms.
directory_entry file_size : 1200128 in 0 ms.
It’s interesting to see that the directory_entry
method is almost
no-op in comparison to other techniques. But I haven’t measured the
first time access.
Summary
In this blog post, we’ve shed some light over the file_size
function.
We covered permissions that are required to get the file status and also
compared the performance.
While getting a file size is probably not the crucial part of your
application it was an interesting lesson about some bits of
std::filesystem
. In the next posts, I’ll hope to cover more stuff in
that area.
I've prepared a valuable bonus if you're interested in Modern C++!
Learn all major features of recent C++ Standards!
Check it out here: