现代 C++ 实战(07):C++17 工具箱
C++17 不像 C++11 / C++20 那样「改天换地」,却塞进了大量日常极好用的工具:std::any、std::filesystem、结构化绑定、if constexpr、折叠表达式、并行算法……写业务代码时经常伸手就能摸到。
这一篇把 C++17 工具箱里最常用的一批串起来,对应三个 demo:any_demo、filesystem_demo、parallel_algo_demo。
这是「现代 C++ 实战」系列的第 7 篇。建议先读 第 06 篇:Lambda 与类型推导。
一、std::any:运行期存任意类型
C++17 的 std::any 是类型擦除容器——运行期可以换成 int、string、vector……类型信息保存在内部,取出时用 std::any_cast。
1 |
|
| 成员 / 操作 | 作用 |
|---|---|
has_value() |
是否为空 |
type() |
返回 type_info |
reset() |
清空 |
any_cast<T>(a) |
按类型取出(引用版抛异常,指针版返回 nullptr) |
与 void*、std::variant 怎么选?
void* |
std::variant<A,B,C> |
std::any |
|
|---|---|---|---|
| 类型信息 | 无 | 编译期固定集合 | 运行期任意 |
| 内存 | 手动管理 | 通常栈上、无堆 | 大对象可能堆分配 |
| 典型场景 | C 遗留接口 | 状态机、有限联合 | 配置表、插件返回值 |
选型:类型集合编译期已知 → variant(更快);运行期才知道类型 → any。
demo 按「基础用法 → any_cast → 成员函数 → 对比 void* → 对比 variant → 实际场景」六段展开,最后用 std::map<std::string, std::any> 实现简易配置类 Config——set("port", 8080)、get<int>("port")、get_or_default<int>("missing", 0),再加一个返回 std::any 的 MathPlugin 插件示例,见 ref/cpp_demo/basics/any_demo/。
二、std::filesystem:跨平台文件系统
C++17 把文件系统操作收进标准库,告别「Windows 用一套 API、Linux 用另一套」:
1 |
|
| 常用 API | 用途 |
|---|---|
fs::path |
路径拼接 a / b、取 stem() / extension() |
exists / file_size / last_write_time |
属性查询 |
directory_iterator |
单层目录遍历 |
recursive_directory_iterator |
递归遍历 |
create_directories / remove_all |
创建 / 删除 |
copy / copy_file / rename |
复制与重命名 |
path 自动处理 / 与 \,同一套代码在 macOS / Linux / Windows 上都能编译(GCC 9 之前需额外链接 -lstdc++fs,LLVM 9 之前的 libc++ 需 -lc++fs;当前工具链都不需要)。
完整示例见 ref/cpp_demo/basics/filesystem_demo/:覆盖路径分解与拼接、状态查询、目录遍历(单层 / 递归 / 过滤)、创建复制重命名删除、fs::space 磁盘空间、fs::permissions 权限修改,以及异常与 std::error_code 两种错误处理方式。遍历和文件操作会在当前目录下创建 fs_demo_temp、fs_ops_temp 等临时目录,演示结束后自行清理。
三、结构化绑定:一次拆多个返回值
C++17 允许把 pair、tuple、数组、结构体成员一次性绑定到多个变量:
1 | std::map<std::string, int> m{{"a", 1}, {"b", 2}}; |
配合 range-for 遍历 map 特别顺手——不用再写 .first / .second。
四、if constexpr:编译期分支
模板里按类型走不同逻辑,C++17 之前要靠 SFINAE;if constexpr 让不走的分支不参与实例化:
1 | template<typename T> |
if constexpr 在编译期决定走哪条分支;普通 if 两条分支都必须能编译通过。
五、折叠表达式:可变参数一行求和
C++17 折叠表达式简化了可变参数模板:
1 | template<typename... Args> |
| 形式 | 示例 | 含义 |
|---|---|---|
| 一元右折叠 | (args + ...) |
arg1 + (arg2 + (...)) |
| 一元左折叠 | (... + args) |
((...) + arg2) + arg1 |
| 二元折叠 | (args + ... + 0) |
带初始值 |
六、并行算法:std::execution::par
C++17 为部分 STL 算法增加了执行策略参数:
1 |
|
| 策略 | 含义 |
|---|---|
std::execution::seq |
顺序(默认行为) |
std::execution::par |
允许多线程并行 |
std::execution::par_unseq |
并行 + 允许 SIMD 向量化 |
注意:
- 数据量小时并行反而更慢(线程开销)
par_unseq要求操作无锁、无分配——纯计算最合适- Apple Clang / libc++ 可能头文件存在但未真正实现并行策略;demo 用
__cpp_lib_parallel_algorithm宏检测(定义HAS_EXECUTION_POLICY),不支持时回退顺序执行
并行算法的调度由标准库实现负责,不是你自己写的线程池(系列第 14 篇会讲自建线程池)。GCC 的 libstdc++ 以 Intel TBB 作为并行后端,所以 demo 的 CMake 在 Threads::Threads 之外还会 find_package(TBB QUIET),找到就链接 TBB::tbb;Docker 镜像已预装 libtbb-dev。
性能对比见 ref/cpp_demo/basics/parallel_algo_demo/——依次演示并行 sort(100 万元素)、for_each、transform、reduce(1000 万元素)、transform_reduce、find_if / count_if,最后在 10 万到 1000 万五档数据量下对比 accumulate 与并行 reduce,全部用 chrono 计时并打印加速比。
七、三个 demo 怎么跑
1 | cd ref/cpp_demo/basics/any_demo && ./build.sh --run |
三个 demo 的 CMake 都是 set(CMAKE_CXX_STANDARD 17);filesystem 在 GCC 8 及更早版本需要额外链接 stdc++fs,当前工具链无需处理。
八、小结
| 特性 | 一句话 |
|---|---|
std::any |
运行期任意类型,配置 / 插件场景 |
std::filesystem |
跨平台路径与文件操作 |
| 结构化绑定 | auto [k,v] 解构 map / pair |
if constexpr |
模板内编译期分支 |
| 折叠表达式 | (args + ...) 简化可变参数 |
| 并行算法 | std::execution::par 加速大数组算法 |
现代 C++ 实战系列第 7 篇完。下一篇 错误处理策略——错误码、异常、optional、expected 各走哪条路。
系列导航
| 篇号 | 标题 | 状态 |
|---|---|---|
| 06 | Lambda 与类型推导 | ✅ |
| 07 | C++17 工具箱(本篇) | ✅ |
| 08 | 错误处理策略 | ✅ |











