Move Files Safely in C++17 with std::filesystem
Need to move files in a C++17 project? std::filesystem gives you type‑safe paths, clear error handling, and cross‑platform behavior. This post walks through a real example, covers permissions, and notes the GCC‑8 linking quirk.
31 Dec 2025, 17:33 UTC

Problem: Moving Files the Old‑School Way
In many legacy C++ codebases you’ll find file moves implemented with rename() or std::system("mv …"). These C‑style APIs work but they expose raw char* strings, hide platform differences, and throw vague errors. When a move fails you often get a generic errno value and must parse it yourself. Worse, permissions and ownership are lost unless you explicitly re‑apply them.
Why std::filesystem?
C++17 introduced std::filesystem to replace the fragmented POSIX and Windows APIs. The key benefits for moving files are:
- Type safety:
std::filesystem::pathis a strongly‑typed object, not a plain string. The compiler can catch mismatches and you get convenient operators like/for path concatenation. - Explicit error handling: Operations throw
std::filesystem::filesystem_erroror return anstd::error_codewhen you passstd::filesystem::error_code&. You never get an ambiguouserrno. - Cross‑platform: The same code compiles on Linux, macOS, and Windows. The library hides the underlying
rename()vsMoveFileExdifferences. - Permission preservation:
permissions()lets you query and set file mode bits, so you can copy the original attributes after a move.
Worked Example: Move a File and Keep Its Permissions
The following program moves source.txt to target_dir/ while preserving the original Unix mode bits. It demonstrates both exception‑based and std::error_code error handling.
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main(int argc, char* argv[])
{
if (argc != 3) {
std::cerr << "Usage: move_file <source> <destination_dir>\n";
return 1;
}
fs::path src{argv[1]};
fs::path dst_dir{argv[2]};
fs::path dst = dst_dir / src.filename(); // preserve filename
// 1. Read original permissions
fs::perms orig_perms;
try {
orig_perms = fs::status(src).permissions();
} catch (const fs::filesystem_error& e) {
std::cerr << "Failed to read source permissions: " << e.what() << "\n";
return 1;
}
// 2. Perform the move
std::error_code ec;
fs::rename(src, dst, ec); // non‑throwing overload
if (ec) {
std::cerr << "Move failed: " << ec.message() << " (code " << ec.value() << ")\n";
return 1;
}
// 3. Restore permissions on the new location
ec.clear();
fs::permissions(dst, orig_perms, ec);
if (ec) {
std::cerr << "Failed to set permissions on destination: " << ec.message() << "\n";
return 1;
}
std::cout << "Moved " << src << " to " << dst << "\n";
return 0;
}
Compile with a modern compiler:
g++ -std=c++17 -Wall -Wextra -pedantic -O2 move_file.cpp -o move_file
On GCC 8 or earlier you must add -lstdc++fs to the link line:
g++ -std=c++17 -Wall -Wextra -pedantic -O2 move_file.cpp -o move_file -lstdc++fs
Run it on Linux or Windows:
./move_file /tmp/source.txt /tmp/target_dir
After execution, source.txt no longer exists in /tmp and a copy named source.txt resides in /tmp/target_dir with the same mode bits as the original.
Trade‑Offs and Caveats
- Compiler support: Full
std::filesystemis only available in compilers that fully implement C++17. Older GCC releases (<9) require the-lstdc++fsflag and may have incomplete features. - Runtime overhead: Path resolution and permission queries involve system calls. The overhead is negligible for typical file moves but can be noticeable in tight loops that process thousands of files.
- Platform differences: While
std::filesystemabstracts most differences, certain edge cases (e.g., Windows junction points vs Unix symlinks) still require platform‑specific checks if your code must preserve those semantics.
Actionable Takeaway
Replace old C‑style file moves with std::filesystem::rename or copy_file wherever possible. The type‑safe path object, explicit error codes, and built‑in permission handling give you clearer, more maintainable code. If you target GCC 8 or earlier, remember to link with -lstdc++fs and test on all target platforms. Finally, always validate the result by checking that the destination exists and that its attributes match expectations.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.