现在让我们设好番茄钟放一首好听的音乐开始学习吧 🌈 😋
📚 导读
- 了解为什么要关注 C++17 的
std::filesystem和并行算法 - 快速掌握常用 API 与并行执行策略
- 通过实战示例感受“文件系统 + 并行”的威力
📌 一、为什么要关心 C++17 的这两部分?
在 C++17 之前,标准库对 文件系统操作 和 并行计算 支持都比较薄弱:
- 操作文件和目录通常依赖平台 API(如 POSIX、Win32)或 Boost.Filesystem。
- 想利用多核 CPU,往往要手写线程池或使用第三方库。
C++17 引入了两块关键能力:
std::filesystem:跨平台的标准文件系统库。std::execution并行算法:在熟悉的<algorithm>基础上,通过执行策略轻松启用并行。
这两者结合,可以很方便地写出“既能遍历磁盘、又能吃满多核 CPU”的实用程序。
📁 二、std::filesystem:现代 C++ 的文件系统库
🔧 2.1 基本概念与命名空间
概念讲解
- 头文件:
<filesystem> - 命名空间:
std::filesystem(通常起个别名fs) - 常用类型:
fs::path:表示路径(文件 / 目录)fs::directory_entry:目录中的一个条目fs::directory_iterator:遍历目录fs::recursive_directory_iterator:递归遍历目录树
代码示例
#include <filesystem>
namespace fs = std::filesystem;
fs::path p = "logs";
p /= "2026";
p /= "app.log";
🧱 2.2 fs::path:优雅处理路径
概念讲解
fs::path 用来安全、可移植地表示路径,支持拆分、拼接和转换字符串。
常见需求:
- 获取父目录 / 文件名 / 后缀名
- 构造多级路径
- 与
std::string互转
代码示例
fs::path p{"./data/file.txt"};
auto parent = p.parent_path(); // ./data
auto filename = p.filename(); // file.txt
auto stem = p.stem(); // file
auto ext = p.extension(); // .txt
std::string s = p.string();
fs::path p = "logs";
p /= "2026";
p /= "app.log";
std::string s = p.string();
📂 2.3 文件与目录的基本操作
概念讲解
常见操作:
- 检查路径是否存在、是否为文件 / 目录
- 创建目录(含递归创建)
- 删除文件或整个目录树
- 拷贝、重命名、移动
代码示例
fs::path p{"example.txt"};
if (fs::exists(p)) {
if (fs::is_regular_file(p)) {
// 普通文件
} else if (fs::is_directory(p)) {
// 目录
}
}
// 创建目录
fs::create_directory("output");
if (fs::exists(p)) {
if (fs::is_regular_file(p)) {
// 普通文件
} else if (fs::is_directory(p)) {
// 目录
}
}
fs::path p{"example.txt"};
// 递归创建多级目录
fs::create_directories("output/images/2026");
// 递归创建多级目录
fs::create_directories("output/images/2026");
// 删除单个文件或空目录
fs::remove("output/tmp.txt");
// 递归删除目录树
fs::remove_all("output/images");
// 创建目录
fs::create_directory("output");
// 删除单个文件或空目录
fs::remove("output/tmp.txt");
// 递归删除目录树
fs::remove_all("output/images");
// 拷贝文件(若存在则覆盖)
fs::copy("a.txt", "b.txt",
fs::copy_options::overwrite_existing);
// 重命名 / 移动
fs::rename("old_name.txt", "new_name.txt");
🔍 2.4 遍历目录与递归遍历
概念讲解
- 使用
fs::directory_iterator遍历当前目录 - 使用
fs::recursive_directory_iterator递归遍历目录树 - 搭配条件判断筛选特定后缀(如
.txt/.log)
代码示例
for (const auto &entry : fs::directory_iterator{"."}) {
std::cout << entry.path() << "n";
}
for (const auto &entry : fs::recursive_directory_iterator{"./data"}) {
std::cout << entry.path() << "n";
}
🛡️ 2.5 文件状态与权限:fs::status + fs::permissions
概念讲解
fs::file_status:保存文件的“类型 + 权限”等元信息fs::status(path):访问实际文件系统,返回file_status- 常见文件类型判断:
fs::is_regular_file(st)fs::is_directory(st)fs::is_symlink(st)
- 权限使用
fs::perms枚举表示,例如:owner_read / owner_write / owner_execgroup_read / group_write / group_execothers_read / others_write / others_exec
- 修改权限使用
fs::permissions(path, perms, fs::perm_options::xxx)replace:直接覆盖现有权限add:在原有基础上增加remove:在原有基础上移除
代码示例:查看类型 + 打印权限 + 修改权限
#include <filesystem>
#include <iostream>
#include <fstream>
namespace fs = std::filesystem;
void print_perms(fs::perms p) {
using fs::perms;
auto show = [=](char op, perms perm) {
std::cout << ((p & perm) == perms::none ? '-' : op);
};
show('r', perms::owner_read);
show('w', perms::owner_write);
show('x', perms::owner_exec);
std::cout << ' ';
show('r', perms::group_read);
show('w', perms::group_write);
show('x', perms::group_exec);
std::cout << ' ';
show('r', perms::others_read);
show('w', perms::others_write);
show('x', perms::others_exec);
std::cout << "n";
}
int main() {
fs::path p{"config/app.ini"};
std::ofstream(p).close(); // 确保文件存在
// 1. 获取状态并判断类型
fs::file_status st = fs::status(p);
if (fs::is_regular_file(st)) {
std::cout << p << " is a regular filen";
}
// 2. 打印当前权限
std::cout << "perms: ";
print_perms(st.permissions());
// 3. 为 owner 添加写权限,为 others 移除写权限
fs::permissions(p,
fs::perms::owner_write,
fs::perm_options::add);
fs::permissions(p,
fs::perms::others_write,
fs::perm_options::remove);
std::cout << "after change: ";
print_perms(fs::status(p).permissions());
}
小贴士:Windows 下权限本质是 ACL,
fs::perms只是一个抽象映射;想做精细权限控制时,仍需要调用平台专用 API。
📏 2.6 示例:统计目录下所有文件总大小(顺序版本)
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::path root{"./data"};
std::uintmax_t total_size = 0;
for (const auto &entry : fs::recursive_directory_iterator{root}) {
if (fs::is_regular_file(entry.path())) {
total_size += fs::file_size(entry.path());
}
}
namespace fs = std::filesystem;
int main() {
fs::path root{"./data"};
std::uintmax_t total_size = 0;
for (const auto &entry : fs::recursive_directory_iterator{root}) {
if (fs::is_regular_file(entry.path())) {
total_size += fs::file_size(entry.path());
}
}
std::cout << "Total size: " << total_size << " bytesn";
}
#include <filesystem>
#include <iostream>
std::cout << "Total size: " << total_size << " bytesn";
}
这已经是很多工具脚本的雏形了,后面我们会用并行算法把这种任务“多核加速”。
⚡ 三、C++17 并行算法与 <execution>
🚦 3.1 执行策略(Execution Policy)
概念讲解
C++17 定义了三种主要执行策略:
std::execution::seq:顺序执行,行为与传统<algorithm>一致。std::execution::par:允许并行执行,通常使用多个线程。std::execution::par_unseq:并行 + 向量化(SIMD),要求算法对顺序不敏感且无数据竞争。
代码示例
#include <execution>
#include <algorithm>
#include <vector>
std::vector<int> v = {/* ... */};
// 顺序
std::sort(std::execution::seq, v.begin(), v.end());
// 并行排序
std::sort(std::execution::par, v.begin(), v.end());
#include <execution>
#include <algorithm>
#include <vector>
std::vector<int> v = {/* ... */};
// 顺序
std::sort(std::execution::seq, v.begin(), v.end());
// 并行排序
std::sort(std::execution::par, v.begin(), v.end());
📚 3.2 并行算法到底在干什么?(核心概念)
可以把并行算法 + 执行策略理解成:
把「一段区间上的同构操作」拆分成多块,交给多个线程同时处理,最后把各块结果合并。
- 输入仍然是一对迭代器
[first, last),比如vector/array/ 普通数组 - 算法本身还是熟悉的那几个:
sort/for_each/transform/reduce/transform_reduce等 - 区别只在于:
- 顺序策略:按迭代顺序,一个元素一个元素处理
- 并行策略:把区间切成若干子区间,在不同线程上分别处理,再用“归约操作”合并
reduce 是 C++17 新增的“可并行求和(或一般归约)”算法,语义上是:
init op a[0] op a[1] op ... op a[n-1]
但具体分块方式、合并顺序都不保证,所以要求 op 满足:
- 交换律:
a op b == b op a - 结合律:
(a op b) op c == a op (b op c)
典型用法:
#include <numeric>
#include <execution>
#include <vector>
std::vector<int> v = {1, 2, 3, 4, 5};
// 顺序 reduce(类似 accumulate,但可由库自行分块)
int s1 = std::reduce(std::execution::seq, v.begin(), v.end(), 0);
// 并行 reduce:在多线程上累加
int s2 = std::reduce(std::execution::par, v.begin(), v.end(), 0);
小结:当你只是要把一堆数字、结构体中的某个数值字段做「求和 / 求积 / min / max」这类操作时,
reduce是首选。
很多时候你希望:
- 先把元素变成某个标量(例如:
长度、平方、大小) - 再对这些标量做
reduce
传统写法通常要两步:
// 1. transform 到一个中间容器
// 2. 再对中间容器做 accumulate / reduce
std::transform_reduce 把这两步合成一步,并且对 transform + reduce 整体做并行优化:
#include <numeric>
#include <execution>
#include <vector>
std::vector<int> v = {1, 2, 3, 4};
// 顺序:先平方,再求和
int sum_sq_seq = std::transform_reduce(
std::execution::seq,
v.begin(), v.end(),
0,
std::plus<>{}, // 归约:如何合并两个部分结果
[](int x) { return x * x; } // 变换:元素 -> 标量
);
// 并行版本:把区间切块在线程池中执行
int sum_sq_par = std::transform_reduce(
std::execution::par,
v.begin(), v.end(),
0,
std::plus<>{},
[](int x) { return x * x; }
);
在我们「统计目录树总大小」的示例中:
- 变换函数:
fs::path -> fs::file_size(path) - 归约函数:
std::plus<>(把大小相加)
std::uintmax_t total_size = std::transform_reduce(
std::execution::par,
files.begin(), files.end(),
static_cast<std::uintmax_t>(0),
std::plus<>{},
[](const fs::path& p) {
return fs::file_size(p);
}
);
一句话:
–
<strong>reduce</strong>:对“原始元素”做折叠
–
<strong>transform_reduce</strong>:对“经过一个映射后的值”做折叠,少一次中间容器,适合「路径 -> 文件大小」「结构体 -> 某个字段」这类场景。
📚 3.3 哪些算法支持并行执行策略?
大部分“元素遍历 + 计算”类算法都提供了执行策略重载,例如:
std::for_eachstd::transformstd::sort/std::stable_sortstd::reduce(C++17 新增)std::transform_reducestd::count,std::count_ifstd::find,std::find_ifstd::inclusive_scan/std::exclusive_scan等
#include <execution>
#include <numeric> // std::reduce
#include <vector>
std::vector<int> v = {/* ... */};
// 并行求和
int sum = std::reduce(std::execution::par,
v.begin(), v.end(), 0);
⚠️ 3.4 使用并行算法的注意事项
- 避免数据竞争:Lambda 中不要无保护地写同一共享变量。
- 不要依赖顺序:
par/par_unseq下执行顺序未指定,涉及严格顺序 IO 不适合使用。 - 数据量足够大再并行:小数据集上,并行开销可能超过收益。
🚀 四、std::filesystem + 并行算法:实战示例
📊 4.1 并行统计目录树中文件大小总和
思路:
- 用
std::filesystem递归遍历目录,收集所有文件路径。 - 用
std::transform_reduce+std::execution::par并行计算总大小。
#include <filesystem>
#include <execution>
#include <numeric>
#include <vector>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::path root{"./data"};
std::vector<fs::path> files;
// 1. 收集所有文件路径
for (const auto &entry : fs::recursive_directory_iterator{root}) {
if (fs::is_regular_file(entry.path())) {
files.push_back(entry.path());
}
}
// 2. 并行计算总大小
std::uintmax_t total_size = std::transform_reduce(
std::execution::par,
files.begin(), files.end(),
static_cast<std::uintmax_t>(0),
std::plus<>{},
[](const fs::path &p) {
return fs::file_size(p);
}
);
std::cout << "Total size: " << total_size << " bytesn";
}
在这个例子中:
std::filesystem负责与 OS 打交道。std::transform_reduce负责并行把“路径 → 大小”的映射和值累加起来。
📈 4.2 并行统计某种扩展名文件的数量与平均大小
例如:统计目录下所有 .log 文件数量和平均大小。
#include <filesystem>
#include <execution>
#include <numeric>
#include <vector>
#include <iostream>
namespace fs = std::filesystem;
struct FileInfo {
std::uintmax_t size;
};
int main() {
fs::path root{"./logs"};
std::vector<FileInfo> logs;
for (const auto &entry : fs::recursive_directory_iterator{root}) {
if (fs::is_regular_file(entry.path()) &&
entry.path().extension() == ".log") {
logs.push_back(FileInfo{fs::file_size(entry.path())});
}
}
// 并行统计
std::uintmax_t total_size = std::transform_reduce(
std::execution::par,
logs.begin(), logs.end(),
static_cast<std::uintmax_t>(0),
std::plus<>{},
[](const FileInfo &info) { return info.size; }
);
std::size_t count = logs.size();
double avg = count ? static_cast<double>(total_size) / count : 0.0;
std::cout << "Count: " << count << "n";
std::cout << "Average size: " << avg << " bytesn";
}
你可以轻松把这个逻辑扩展为更复杂的分析(例如区分不同子目录、过滤时间、结合正则匹配文件名等)。
📑 五、常用 std::filesystem / 并行算法函数速查表
| 函数 | 作用概述 |
|---|---|
| 路径与基本信息 | |
fs::current_path() | 获取或设置当前工作目录 |
fs::path 构造 / /= | 表示路径;使用 /= 跨平台拼接子路径 |
p.parent_path() / p.filename() | 获取父目录 / 文件名部分 |
p.stem() / p.extension() | 获取不带扩展名的文件名 / 扩展名 |
fs::absolute(p) / fs::canonical(p) | 转为绝对路径;后者要求路径存在并做规范化 |
fs::relative(target, base) | 计算从 base 到 target 的相对路径 |
| 文件与目录操作 | |
fs::exists(p) | 判断路径是否存在 |
fs::is_regular_file(p) / fs::is_directory(p) | 判断是否为普通文件 / 目录 |
fs::file_size(p) | 获取文件大小(字节) |
fs::last_write_time(p) | 获取 / 设置最后修改时间 |
fs::create_directory(p) / fs::create_directories(p) | 创建单层 / 多层目录 |
fs::copy_file(from, to, opts) / fs::copy(from, to, opts) | 拷贝单个文件 / 拷贝整个目录树(可递归 + 覆盖) |
fs::remove(p) / fs::remove_all(p) | 删除文件或空目录 / 递归删除目录树 |
| 状态与权限 | |
fs::file_status / fs::status(p) | 获取文件状态(类型 + 权限) |
fs::perms | 权限枚举(owner/group/others 的 r/w/x) |
fs::permissions(p, perms, opts) | 按选项 replace/add/remove 设置或修改权限 |
fs::is_symlink(st) / fs::is_empty(p) | 判断是否为符号链接 / 文件或目录是否为空 |
fs::directory_options::skip_permission_denied | 在递归遍历时跳过权限不足的路径 |
| 目录遍历 | |
fs::directory_iterator(dir) | 遍历目录的直接子项(非递归) |
fs::recursive_directory_iterator(dir) | 递归遍历目录树 |
begin(it) / end(it) | 与范围 for 等价的显式迭代器遍历方式 |
fs::directory_entry | 目录中的单个条目,提供 path()、is_directory() 等接口 |
| 并行算法与执行策略 | |
std::execution::seq / par / par_unseq | 顺序 / 并行 / 并行 + SIMD 的执行策略 |
std::sort(policy, first, last) | 按给定策略排序(支持 seq / par / par_unseq) |
std::reduce(policy, first, last, init) | 对区间做归约(求和、求积、min/max 等),可并行 |
std::transform_reduce(policy, first, last, init, binary_op, unary_op) | 先用 unary_op 映射元素,再用 binary_op 做归约,常用于“映射 + 累加” |
std::for_each(policy, first, last, f) | 对区间内每个元素执行操作 f,可并行(注意避免数据竞争) |
std::count / std::count_if / std::find_if 等 | 按策略执行的计数 / 查找类算法,适合大数据集并行扫描 |
到这里,std::filesystem 的常见用法、权限/状态操作,以及与 C++17 并行算法(特别是 reduce / transform_reduce)的组合使用,就基本成体系了,可以直接拿去写工具脚本、日志分析器或者磁盘扫描类的小项目。
优先考虑使用 C++17 并行算法的场景:
- 任务可以抽象为“对一个范围内的元素做同构操作”。
- 不需要严格的处理顺序。
- 任务偏 CPU 密集(计算量较大)。
更适合自定义线程池 / 任务系统的场景:
- 需要复杂的任务依赖关系。
- 需要精细控制线程数量、绑定等。
- 涉及大量 IO、多种异步事件(网络、定时器等)。
如果要把这篇内容整理成博客,可以按下面的结构:
- 背景动机:C++17 解决哪些痛点(文件系统操作、并行计算)。
<strong>std::filesystem</strong>基本用法:path、遍历、创建 / 删除、拷贝等。- 并行算法与执行策略:
std::execution三种策略 + 常用算法。 - 综合示例:用
std::filesystem收集数据,用并行算法处理。 - 实践注意事项:数据竞争、性能、平台支持等。

