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
2
3
4
5
6
7
8
9
10
11
#include <any>
#include <string>
#include <vector>

std::any value = 42;
int n = std::any_cast<int>(value); // 42;类型不对 → 抛 bad_any_cast

value = std::string("hello"); // 可以改存别的类型
value.emplace<std::vector<int>>({1, 2, 3});

if (auto* p = std::any_cast<int>(&value)) { /* 安全,失败返回 nullptr(此处为 nullptr) */ }
成员 / 操作 作用
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <filesystem>
namespace fs = std::filesystem;

fs::path p = fs::current_path() / "data" / "config.json";
std::cout << p.filename() << p.extension() << '\n';

if (fs::exists(p)) {
std::cout << "size: " << fs::file_size(p) << '\n';
}

for (const auto& entry : fs::directory_iterator("logs")) {
if (entry.is_regular_file())
std::cout << entry.path() << '\n';
}

fs::create_directories("out/cache");
fs::copy_file("a.txt", "out/cache/a.txt", fs::copy_options::overwrite_existing);
常用 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
2
3
4
5
6
7
8
9
10
std::map<std::string, int> m{{"a", 1}, {"b", 2}};
for (const auto& [key, value] : m) {
std::cout << key << " -> " << value << '\n';
}

std::pair<int, std::string> p{42, "ok"};
auto [code, msg] = p;

int arr[] = {1, 2, 3};
auto [x, y, z] = arr;

配合 range-for 遍历 map 特别顺手——不用再写 .first / .second。

四、if constexpr:编译期分支

模板里按类型走不同逻辑,C++17 之前要靠 SFINAE;if constexpr 让不走的分支不参与实例化:

1
2
3
4
5
6
7
8
9
10
template<typename T>
auto process(T value) {
if constexpr (std::is_integral_v<T>) {
return value * 2;
} else if constexpr (std::is_floating_point_v<T>) {
return value * 1.5;
} else {
return value;
}
}

if constexpr 在编译期决定走哪条分支;普通 if 两条分支都必须能编译通过。

五、折叠表达式:可变参数一行求和

C++17 折叠表达式简化了可变参数模板:

1
2
3
4
5
6
7
8
9
10
11
template<typename... Args>
auto sum(Args... args) {
return (args + ...); // 一元右折叠
}

sum(1, 2, 3, 4); // 10

template<typename... Args>
bool all_true(Args... args) {
return (args && ...);
}
形式 示例 含义
一元右折叠 (args + ...) arg1 + (arg2 + (...))
一元左折叠 (... + args) ((...) + arg2) + arg1
二元折叠 (args + ... + 0) 带初始值

六、并行算法:std::execution::par

C++17 为部分 STL 算法增加了执行策略参数:

1
2
3
4
5
6
7
8
9
10
11
#include <algorithm>
#include <execution>
#include <vector>

std::vector<int> v(1'000'000);
std::sort(std::execution::par, v.begin(), v.end());

std::vector<int> a(1'000'000, 3), b(a.size());
std::transform(std::execution::par_unseq,
a.begin(), a.end(), b.begin(),
[](int x) { return x * x; });
策略 含义
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
2
3
cd ref/cpp_demo/basics/any_demo && ./build.sh --run
cd ref/cpp_demo/basics/filesystem_demo && ./build.sh --run
cd ref/cpp_demo/basics/parallel_algo_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 错误处理策略 ✅