C++17 新特性全面总结
核心思想:C++17 是现代 C++ 的又一次重大升级,在语言层面引入了结构化绑定、constexpr if、折叠表达式、类模板参数推导(CTAD)等强力特性,在标准库层面补齐了 std::optional、std::variant、std::any、std::string_view、std::filesystem、并行算法等长期缺失的基础设施。C++17 让 C++ 在表达力、安全性和开发效率上全面迈进了一大步。
🚀 1. 结构化绑定(Structured Bindings)
1.1 基本用法:
- 将聚合类型、tuple、pair、数组等的成员一次性绑定到具名变量
- 极大简化了多返回值的使用方式
C++
#include <map>
#include <tuple>
// ❌ C++11/14:解包 pair 和 tuple 非常繁琐
void old_style() {
std::map<std::string, int> scores = {{"Alice", 90}, {"Bob", 85}};
for (const auto& pair : scores) {
std::cout << pair.first << ": " << pair.second << "\n"; // .first .second
}
auto t = std::make_tuple(1, 3.14, "hello");
int x = std::get<0>(t);
double y = std::get<1>(t);
}
// ✅ C++17:结构化绑定
void structured_bindings() {
// 绑定 map 元素
std::map<std::string, int> scores = {{"Alice", 90}, {"Bob", 85}};
for (const auto& [name, score] : scores) {
std::cout << name << ": " << score << "\n"; // 直接用名字!
}
// 绑定 tuple
auto [x, y, z] = std::make_tuple(1, 3.14, std::string("hello"));
std::cout << x << ", " << y << ", " << z << "\n"; // 1, 3.14, hello
// 绑定 pair(insert 返回 pair<iterator, bool>)
auto [it, success] = scores.insert({"Charlie", 95});
if (success) {
std::cout << "Inserted: " << it->first << "\n";
}
// 绑定数组
int arr[] = {10, 20, 30};
auto [a, b, c] = arr;
// 绑定结构体
struct Point { double x, y, z; };
Point p{1.0, 2.0, 3.0};
auto [px, py, pz] = p;
}
1.2 引用绑定与修改:
C++
void binding_references() {
std::pair<int, std::string> data{42, "hello"};
// const 引用绑定(只读)
const auto& [id, name] = data;
// 非 const 引用绑定(可修改原对象)
auto& [id_ref, name_ref] = data;
id_ref = 100;
name_ref = "world";
std::cout << data.first << ", " << data.second << "\n"; // 100, world
// 值绑定(拷贝)
auto [id_copy, name_copy] = data;
id_copy = 999; // 不影响 data
// 实际应用:同时获取 map 的 key 和 value 并修改
std::map<std::string, int> m = {{"a", 1}, {"b", 2}};
for (auto& [key, value] : m) {
value *= 10; // 修改 map 中的值
}
}
// 自定义类型支持结构化绑定(通过 tuple 协议)
class Color {
uint8_t r_, g_, b_;
public:
Color(uint8_t r, uint8_t g, uint8_t b) : r_(r), g_(g), b_(b) {}
template <size_t I>
auto get() const {
if constexpr (I == 0) return r_;
else if constexpr (I == 1) return g_;
else return b_;
}
};
// 需要特化 tuple_size 和 tuple_element
namespace std {
template <> struct tuple_size<Color> : integral_constant<size_t, 3> {};
template <size_t I> struct tuple_element<I, Color> { using type = uint8_t; };
}
void custom_binding() {
Color c{255, 128, 0};
auto [r, g, b] = c;
std::cout << (int)r << ", " << (int)g << ", " << (int)b << "\n"; // 255, 128, 0
}
⚡ 2. constexpr if ------ 编译期条件分支
2.1 基本语法与动机:
C++
#include <type_traits>
#include <string>
// ❌ C++14:使用 SFINAE 或标签分发,代码复杂
template <typename T>
typename std::enable_if<std::is_integral<T>::value, std::string>::type
toString_14(T value) {
return std::to_string(value);
}
template <typename T>
typename std::enable_if<std::is_floating_point<T>::value, std::string>::type
toString_14(T value) {
return std::to_string(value) + "f";
}
// ✅ C++17:constexpr if 在编译期选择分支,未选中的分支不实例化
template <typename T>
std::string toString_17(T value) {
if constexpr (std::is_integral_v<T>) {
return std::to_string(value);
} else if constexpr (std::is_floating_point_v<T>) {
return std::to_string(value) + "f";
} else if constexpr (std::is_same_v<T, std::string>) {
return value;
} else {
static_assert(sizeof(T) == 0, "Unsupported type"); // 编译期报错
}
}
void constexpr_if_example() {
std::cout << toString_17(42) << "\n"; // "42"
std::cout << toString_17(3.14) << "\n"; // "3.140000f"
std::cout << toString_17(std::string("hi")) << "\n"; // "hi"
}
2.2 替代标签分发与 SFINAE:
C++
// 迭代器 advance 的经典实现
// ❌ C++14 标签分发
namespace old_way {
template <typename It>
void advance_impl(It& it, int n, std::random_access_iterator_tag) {
it += n; // O(1)
}
template <typename It>
void advance_impl(It& it, int n, std::input_iterator_tag) {
while (n-- > 0) ++it; // O(n)
}
template <typename It>
void advance(It& it, int n) {
advance_impl(it, n, typename std::iterator_traits<It>::iterator_category{});
}
}
// ✅ C++17 constexpr if:单一函数,简洁明了
namespace new_way {
template <typename It>
void advance(It& it, int n) {
using category = typename std::iterator_traits<It>::iterator_category;
if constexpr (std::is_base_of_v<std::random_access_iterator_tag, category>) {
it += n; // 随机访问迭代器:O(1)
} else {
while (n-- > 0) ++it; // 其他迭代器:O(n)
}
}
}
// 可变参数模板递归终止更简洁
template <typename T, typename... Args>
void print(const T& first, const Args&... rest) {
std::cout << first;
if constexpr (sizeof...(rest) > 0) { // ✅ 编译期判断,无需单独的终止函数
std::cout << ", ";
print(rest...);
}
// 当 rest 为空时,这个分支根本不会实例化
}
2.3 编译期多态:
C++
template <typename T>
class SmartSerializer {
public:
std::string serialize(const T& obj) {
if constexpr (std::is_arithmetic_v<T>) {
return std::to_string(obj);
} else if constexpr (std::is_same_v<T, std::string>) {
return "\"" + obj + "\"";
} else if constexpr (std::is_same_v<T, bool>) {
return obj ? "true" : "false";
} else if constexpr (requires { obj.to_json(); }) { // C++20 概念预览
return obj.to_json();
} else {
// 未选中的分支中可以出现对 T 无效的代码
// 它们不会被实例化,因此不会引发编译错误
return "<unknown>";
}
}
};
🔄 3. 折叠表达式(Fold Expressions)
C++
/*
* 折叠表达式的四种形式:
* ┌─────────────────┬───────────────────────────────────────────┐
* │ 一元左折叠 │ (... op args) → ((a1 op a2) op a3) │
* │ 一元右折叠 │ (args op ...) → (a1 op (a2 op a3)) │
* │ 二元左折叠 │ (init op ... op args) → ((init op a1) op a2)│
* │ 二元右折叠 │ (args op ... op init) → (a1 op (a2 op init))│
* └─────────────────┴───────────────────────────────────────────┘
*/
// ❌ C++14:可变参数求和需要递归
template <typename T>
T sum_14(T val) { return val; }
template <typename T, typename... Args>
T sum_14(T first, Args... rest) { return first + sum_14(rest...); }
// ✅ C++17:折叠表达式一行搞定
template <typename... Args>
auto sum(Args... args) {
return (... + args); // 一元左折叠:((a1 + a2) + a3) + ...
}
template <typename... Args>
auto product(Args... args) {
return (... * args); // ((a1 * a2) * a3) * ...
}
// 带初始值的二元折叠
template <typename... Args>
auto sum_from(int init, Args... args) {
return (init + ... + args); // 二元左折叠
}
void fold_examples() {
std::cout << sum(1, 2, 3, 4, 5) << "\n"; // 15
std::cout << product(1, 2, 3, 4, 5) << "\n"; // 120
std::cout << sum_from(100, 1, 2, 3) << "\n"; // 106
}
// 逻辑折叠
template <typename... Args>
bool all_true(Args... args) {
return (... && args); // a1 && a2 && a3 && ...
}
template <typename... Args>
bool any_true(Args... args) {
return (... || args);
}
// 逗号折叠:对每个参数执行操作
template <typename... Args>
void print_all(const Args&... args) {
(std::cout << ... << args) << "\n"; // 连续输出
}
// 更优雅的带分隔符打印
template <typename T, typename... Args>
void print_sep(const T& first, const Args&... rest) {
std::cout << first;
((std::cout << ", " << rest), ...); // 逗号折叠
std::cout << "\n";
}
// 实际应用:检查容器是否包含所有指定值
template <typename Container, typename... Values>
bool contains_all(const Container& c, const Values&... vals) {
return (... && (std::find(c.begin(), c.end(), vals) != c.end()));
}
// 实际应用:向容器插入多个元素
template <typename Container, typename... Values>
void insert_all(Container& c, Values&&... vals) {
(c.push_back(std::forward<Values>(vals)), ...); // 逗号折叠
}
void practical() {
std::vector<int> v = {1, 2, 3, 4, 5};
std::cout << contains_all(v, 1, 3, 5) << "\n"; // true
std::cout << contains_all(v, 1, 6) << "\n"; // false
insert_all(v, 6, 7, 8);
print_sep(1, "hello", 3.14, 'A'); // 1, hello, 3.14, A
}
🏗️ 4. 类模板参数推导(CTAD)
C++
// ✅ C++17:构造对象时无需显式指定模板参数,编译器自动推导
// ❌ C++14:必须显式指定模板参数或使用 make_* 辅助函数
std::pair<int, double> p1(42, 3.14);
auto p2 = std::make_pair(42, 3.14);
// ✅ C++17:直接推导
std::pair p3(42, 3.14); // pair<int, double>
std::tuple t(1, 2.5, "hello"); // tuple<int, double, const char*>
std::vector v{1, 2, 3, 4}; // vector<int>
std::optional opt(42); // optional<int>
// 更多 CTAD 示例
void ctad_examples() {
// 容器
std::vector v1 = {1, 2, 3}; // vector<int>
std::vector v2(v1.begin(), v1.end()); // vector<int>
std::array a = {1, 2, 3, 4, 5}; // array<int, 5>
// 智能指针(部分场景)
// std::unique_ptr up(new int(42)); // ❌ 不支持(有歧义)
// 锁
std::mutex mtx;
std::lock_guard lg(mtx); // lock_guard<mutex>
std::unique_lock ul(mtx); // unique_lock<mutex>
// 函数对象
std::function f = [](int x) { return x * 2; }; // function<int(int)>
// 复数
std::complex c(3.0, 4.0); // complex<double>
}
// 自定义推导指引(Deduction Guides)
template <typename T>
class MyContainer {
std::vector<T> data_;
public:
MyContainer(std::initializer_list<T> init) : data_(init) {}
template <typename Iter>
MyContainer(Iter begin, Iter end) : data_(begin, end) {}
};
// 推导指引:从迭代器推导出元素类型
template <typename Iter>
MyContainer(Iter, Iter) -> MyContainer<typename std::iterator_traits<Iter>::value_type>;
void deduction_guide_example() {
MyContainer c1 = {1, 2, 3}; // MyContainer<int>
std::vector<double> src = {1.1, 2.2};
MyContainer c2(src.begin(), src.end()); // MyContainer<double>(靠推导指引)
}
🛡️ 5. std::optional
C++
#include <optional>
/*
* std::optional<T>:表示"可能有值,也可能没有"
* ┌────────────────┬───────────────────────────────────┐
* │ 用途 │ 替代指针/哨兵值表示"无结果" │
* │ 状态 │ 有值 (engaged) / 无值 (empty) │
* │ 访问 │ *, ->, value(), value_or() │
* │ 检查 │ has_value(), operator bool │
* │ 开销 │ T + 1字节(对齐后可能更多) │
* └────────────────┴───────────────────────────────────┘
*/
// ❌ C++14:用指针/特殊值表示"没有结果"
int* find_old(const std::vector<int>& v, int target) {
// 返回 nullptr 表示未找到,但需要管理生命周期
return nullptr;
}
// ✅ C++17:用 optional 语义清晰、安全
std::optional<int> find_index(const std::vector<int>& v, int target) {
for (size_t i = 0; i < v.size(); ++i) {
if (v[i] == target) return static_cast<int>(i); // 隐式构造 optional
}
return std::nullopt; // 明确表示"无值"
}
void optional_basic() {
std::vector<int> data = {10, 20, 30, 40};
auto result = find_index(data, 30);
// 检查是否有值
if (result) { // 或 result.has_value()
std::cout << "Found at index: " << *result << "\n"; // 解引用
std::cout << "Found at index: " << result.value() << "\n"; // 安全版本(无值时抛异常)
}
// 提供默认值
auto missing = find_index(data, 99);
int idx = missing.value_or(-1); // 无值时返回 -1
std::cout << "Index: " << idx << "\n"; // -1
}
// 实际应用:配置解析
struct Config {
std::optional<std::string> username;
std::optional<int> port;
std::optional<bool> verbose;
};
Config parse_config() {
Config cfg;
cfg.username = "admin";
cfg.port = 8080;
// verbose 未设置,保持 nullopt
return cfg;
}
void config_example() {
auto cfg = parse_config();
std::string user = cfg.username.value_or("guest");
int port = cfg.port.value_or(80);
bool verbose = cfg.verbose.value_or(false);
std::cout << user << ":" << port
<< (verbose ? " [verbose]" : "") << "\n";
}
// optional 链式操作
std::optional<std::string> get_env(const std::string& name) {
const char* val = std::getenv(name.c_str());
if (val) return std::string(val);
return std::nullopt;
}
std::optional<int> parse_int(const std::string& s) {
try { return std::stoi(s); }
catch (...) { return std::nullopt; }
}
void chain_example() {
// 手动链式(C++23 有 and_then/transform/or_else)
auto port = get_env("PORT");
int p = port ? parse_int(*port).value_or(80) : 80;
}
🔀 6. std::variant
C++
#include <variant>
/*
* std::variant<Types...>:类型安全的联合体
* ┌────────────────┬───────────────────────────────────────────┐
* │ 用途 │ 替代 union、替代继承多态的轻量方案 │
* │ 大小 │ max(sizeof(Types...)) + 类型索引 │
* │ 安全 │ 不会访问错误类型(编译期/运行时保证) │
* │ 访问 │ std::get<T>, std::get<I>, std::visit │
* │ 错误 │ bad_variant_access 异常 │
* └────────────────┴───────────────────────────────────────────┘
*/
void variant_basic() {
// 可以持有 int、double 或 string 中的一个
std::variant<int, double, std::string> v;
v = 42;
std::cout << std::get<int>(v) << "\n"; // 42
std::cout << std::get<0>(v) << "\n"; // 42(按索引)
v = 3.14;
std::cout << std::get<double>(v) << "\n"; // 3.14
// std::get<int>(v); // ❌ 抛出 std::bad_variant_access
v = "hello";
// 安全检查
if (auto* p = std::get_if<std::string>(&v)) {
std::cout << "String: " << *p << "\n";
}
// 当前持有的类型索引
std::cout << "Index: " << v.index() << "\n"; // 2(string 在第3个位置)
std::cout << std::holds_alternative<std::string>(v) << "\n"; // true
}
// std::visit ------ variant 的核心使用模式
void visit_example() {
using Var = std::variant<int, double, std::string>;
std::vector<Var> values = {42, 3.14, std::string("hello"), 100};
// 使用 overloaded Lambda 进行 visit
for (const auto& v : values) {
std::visit([](const auto& val) {
// 泛型 Lambda:编译器为每种类型生成一个版本
std::cout << val << "\n";
}, v);
}
}
// overloaded 惯用法(C++17 实现)
template <class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template <class... Ts> overloaded(Ts...) -> overloaded<Ts...>; // 推导指引
void overloaded_pattern() {
std::variant<int, double, std::string> v = "hello";
std::visit(overloaded{
[](int i) { std::cout << "int: " << i << "\n"; },
[](double d) { std::cout << "double: " << d << "\n"; },
[](const std::string& s){ std::cout << "string: " << s << "\n"; }
}, v);
// 输出:string: hello
}
// 实际应用:表达式树 / AST(替代继承多态)
struct Literal { double value; };
struct Variable { std::string name; };
struct BinaryOp {
char op;
std::shared_ptr<struct Expr> left, right;
};
using Expr = std::variant<Literal, Variable, BinaryOp>;
double evaluate(const Expr& expr, const std::map<std::string, double>& env) {
return std::visit(overloaded{
[](const Literal& lit) { return lit.value; },
[&](const Variable& var) { return env.at(var.name); },
[&](const BinaryOp& bin) {
double l = evaluate(*bin.left, env);
double r = evaluate(*bin.right, env);
switch (bin.op) {
case '+': return l + r;
case '*': return l * r;
default: throw std::runtime_error("Unknown op");
}
}
}, expr);
}
📦 7. std::any 与 std::string_view
7.1 std::any:
C++
#include <any>
/*
* std::any:可以持有任意类型的值
* ┌────────────────┬───────────────────────────────────────┐
* │ vs variant │ variant 类型集固定,any 完全开放 │
* │ vs void* │ any 是类型安全的,void* 不是 │
* │ 开销 │ 小对象可能就地存储(SBO),大对象堆分配 │
* │ 访问 │ std::any_cast<T> │
* └────────────────┴───────────────────────────────────────┘
*/
void any_example() {
std::any a = 42;
std::cout << std::any_cast<int>(a) << "\n"; // 42
a = std::string("hello");
std::cout << std::any_cast<std::string>(a) << "\n"; // hello
a = 3.14;
// 类型检查
std::cout << a.type().name() << "\n"; // 实现定义(如 "d")
std::cout << a.has_value() << "\n"; // true
// 安全访问
auto* p = std::any_cast<double>(&a);
if (p) std::cout << *p << "\n"; // 3.14
// 错误类型抛异常
try {
std::any_cast<int>(a); // ❌ 抛出 std::bad_any_cast
} catch (const std::bad_any_cast& e) {
std::cout << e.what() << "\n";
}
// 清空
a.reset();
std::cout << a.has_value() << "\n"; // false
}
// 实际应用:属性系统 / 配置字典
using PropertyMap = std::map<std::string, std::any>;
void property_system() {
PropertyMap props;
props["name"] = std::string("Widget");
props["width"] = 100;
props["opacity"] = 0.85;
props["visible"] = true;
auto name = std::any_cast<std::string>(props["name"]);
auto width = std::any_cast<int>(props["width"]);
}
7.2 std::string_view ------ 非拥有的字符串视图:
C++
#include <string_view>
/*
* std::string_view:轻量级、不拥有字符串数据的视图
* ┌────────────────┬───────────────────────────────────────────┐
* │ 大小 │ 仅两个成员:指针 + 长度(通常 16 字节) │
* │ 拷贝开销 │ O(1),无论字符串多长 │
* │ 可以指向 │ std::string、C 字符串、子串、字面量 │
* │ 注意 │ 不管理生命周期,被指向的数据必须存活 │
* └────────────────┴───────────────────────────────────────────┘
*/
// ❌ C++14:接受字符串参数时的困境
void process_old(const std::string& s) { /* 如果传 C 串会临时构造 string */ }
void process_old(const char* s) { /* 需要两个重载 */ }
// ✅ C++17:string_view 统一处理
void process(std::string_view sv) {
std::cout << "Length: " << sv.size() << "\n";
std::cout << "Content: " << sv << "\n";
// 丰富的操作(只读)
sv.remove_prefix(1); // 去掉前1个字符
sv.remove_suffix(1); // 去掉后1个字符
auto sub = sv.substr(0, 3); // 子串视图(O(1)!不是拷贝)
auto pos = sv.find("lo");
if (pos != std::string_view::npos) {
std::cout << "Found at: " << pos << "\n";
}
}
void string_view_usage() {
// 无需任何拷贝/转换,全部 O(1)
process("Hello World"); // const char*
process(std::string("Hello World")); // std::string
process(std::string_view("Hello World")); // string_view
// 子串不分配内存
std::string large(1000000, 'x');
std::string_view sv(large);
auto sub = sv.substr(500000, 100); // O(1),不拷贝!
// 对比:large.substr(500000, 100) // O(n),分配+拷贝
}
// ⚠️ 生命周期陷阱
std::string_view dangerous() {
std::string local = "temporary";
return local; // ❌ 悬垂引用!local 销毁后 view 无效
}
std::string_view safe(const std::string& s) {
return s; // ✅ 调用者保证 s 的生命周期
}
/*
* string_view 使用准则:
* ┌──────────────────────────────────────────────────┐
* │ ✅ 用于函数参数(替代 const string&) │
* │ ✅ 用于只读的字符串处理(解析/查找/比较) │
* │ ❌ 不要用于返回值(除非能保证数据存活) │
* │ ❌ 不要用于存储成员(除非明确管理生命周期) │
* │ ❌ 不要指向临时 string │
* └──────────────────────────────────────────────────┘
*/
📂 8. std::filesystem
C++
#include <filesystem>
namespace fs = std::filesystem;
void filesystem_examples() {
// 路径操作
fs::path p = "/home/user/documents/report.txt";
std::cout << "Filename: " << p.filename() << "\n"; // "report.txt"
std::cout << "Stem: " << p.stem() << "\n"; // "report"
std::cout << "Extension: " << p.extension() << "\n"; // ".txt"
std::cout << "Parent: " << p.parent_path() << "\n"; // "/home/user/documents"
// 路径拼接
fs::path dir = "/home/user";
fs::path file = dir / "documents" / "file.txt"; // 使用 / 运算符
// 文件/目录查询
if (fs::exists(p)) {
std::cout << "Size: " << fs::file_size(p) << " bytes\n";
std::cout << "Is file: " << fs::is_regular_file(p) << "\n";
std::cout << "Is dir: " << fs::is_directory(p) << "\n";
}
// 创建目录(包括中间目录)
fs::create_directories("/tmp/my_app/data/cache");
// 复制、移动、删除
fs::copy("source.txt", "dest.txt", fs::copy_options::overwrite_existing);
fs::rename("old_name.txt", "new_name.txt");
fs::remove("temp_file.txt");
fs::remove_all("/tmp/my_app"); // 递归删除
// 获取当前路径和临时路径
std::cout << "Current: " << fs::current_path() << "\n";
std::cout << "Temp: " << fs::temp_directory_path() << "\n";
}
// 遍历目录
void directory_traversal() {
fs::path target = "/home/user/projects";
// 非递归遍历
for (const auto& entry : fs::directory_iterator(target)) {
std::cout << entry.path().filename();
if (entry.is_directory()) std::cout << " [DIR]";
else std::cout << " (" << entry.file_size() << " bytes)";
std::cout << "\n";
}
// 递归遍历
for (const auto& entry : fs::recursive_directory_iterator(target)) {
if (entry.is_regular_file() && entry.path().extension() == ".cpp") {
std::cout << entry.path() << "\n";
}
}
}
// 实际应用:统计项目中的代码行数
std::uintmax_t count_lines(const fs::path& dir, const std::string& ext) {
std::uintmax_t total = 0;
for (const auto& entry : fs::recursive_directory_iterator(dir)) {
if (entry.is_regular_file() && entry.path().extension() == ext) {
std::ifstream file(entry.path());
total += std::count(std::istreambuf_iterator<char>(file),
std::istreambuf_iterator<char>(), '\n');
}
}
return total;
}
// 错误处理(两种方式)
void error_handling() {
// 方式1:异常
try {
fs::copy("nonexistent.txt", "dest.txt");
} catch (const fs::filesystem_error& e) {
std::cerr << e.what() << "\n";
std::cerr << "Path1: " << e.path1() << "\n";
}
// 方式2:错误码(无异常)
std::error_code ec;
fs::copy("nonexistent.txt", "dest.txt", ec);
if (ec) {
std::cerr << "Error: " << ec.message() << "\n";
}
}
🚄 9. 并行算法(Parallel Algorithms)
C++
#include <algorithm>
#include <execution> // 执行策略
#include <numeric>
#include <vector>
/*
* 三种执行策略:
* ┌──────────────────────┬────────────────────────────────────┐
* │ std::execution::seq │ 顺序执行(等同于无策略版本) │
* │ std::execution::par │ 并行执行(多线程) │
* │ std::execution::par_unseq │ 并行 + 向量化(SIMD) │
* └──────────────────────┴────────────────────────────────────┘
*/
void parallel_examples() {
std::vector<int> data(10'000'000);
std::iota(data.begin(), data.end(), 0); // 填充 0, 1, 2, ...
// 并行排序
std::sort(std::execution::par, data.begin(), data.end(),
std::greater<>());
// 并行查找
auto it = std::find(std::execution::par, data.begin(), data.end(), 42);
// 并行变换
std::vector<double> results(data.size());
std::transform(std::execution::par, data.begin(), data.end(),
results.begin(), [](int x) { return std::sqrt(x); });
// 并行归约(C++17 新增,替代 accumulate 的并行版本)
long long sum = std::reduce(std::execution::par,
data.begin(), data.end(), 0LL);
// transform_reduce:并行 map-reduce
double norm = std::transform_reduce(
std::execution::par,
data.begin(), data.end(),
0.0,
std::plus<>(), // reduce 操作
[](int x) { return static_cast<double>(x) * x; } // transform 操作
);
// 并行 for_each
std::for_each(std::execution::par, data.begin(), data.end(),
[](int& x) { x *= 2; });
}
/*
* ⚠️ 并行算法注意事项:
* ┌────────────────────────────────────────────────────────────┐
* │ 1. Lambda/函数对象不能有数据竞争(需要原子操作或无副作用) │
* │ 2. reduce 的操作必须满足交换律和结合律(与 accumulate 不同) │
* │ 3. 小数据集并行可能比串行更慢(线程创建开销) │
* │ 4. 异常行为与串行版本不同(可能调用 std::terminate) │
* └────────────────────────────────────────────────────────────┘
*/
🔧 10. if/switch 初始化语句
C++
// ✅ C++17:在 if/switch 中声明变量,限制作用域
void if_init_examples() {
// ❌ C++14:变量泄漏到外部作用域
{
auto it = myMap.find("key");
if (it != myMap.end()) {
// 使用 it
}
// it 仍然可见(污染外层作用域)
}
// ✅ C++17:变量作用域限制在 if 块内
std::map<std::string, int> myMap = {{"key", 42}};
if (auto it = myMap.find("key"); it != myMap.end()) {
std::cout << "Found: " << it->second << "\n";
}
// it 在这里不可见
// 配合结构化绑定
if (auto [it, ok] = myMap.insert({"new", 100}); ok) {
std::cout << "Inserted: " << it->first << " = " << it->second << "\n";
} else {
std::cout << "Already exists: " << it->second << "\n";
}
// 配合锁
std::mutex mtx;
if (std::lock_guard lg(mtx); true) {
// 在锁保护下执行,lg 在 if 结束时销毁
}
}
// switch 初始化
void switch_init() {
enum class DeviceStatus { Idle, Running, Error };
auto getStatus = []() { return DeviceStatus::Running; };
switch (auto status = getStatus(); status) {
case DeviceStatus::Idle: std::cout << "Idle\n"; break;
case DeviceStatus::Running: std::cout << "Running\n"; break;
case DeviceStatus::Error: std::cout << "Error\n"; break;
}
// status 在这里不可见
}
🏷️ 11. 新增属性
C++
// C++17 新增三个标准属性
// ═══════════ [[nodiscard]] ═══════════
// 标记函数返回值不应被忽略
[[nodiscard]] int compute_result() {
return 42;
}
[[nodiscard("Error code must be checked!")]] // C++20 可加消息
bool try_connect(const std::string& host) {
return false;
}
struct [[nodiscard]] ErrorCode {
int code;
std::string message;
};
ErrorCode process_data() { return {0, "OK"}; }
void nodiscard_example() {
// compute_result(); // ⚠️ 编译器警告:返回值被忽略
int r = compute_result(); // ✅ OK
// try_connect("host"); // ⚠️ 警告:Error code must be checked!
if (try_connect("host")) { /* ... */ } // ✅ OK
// process_data(); // ⚠️ 警告:nodiscard 类型的返回值被忽略
}
// ═══════════ [[maybe_unused]] ═══════════
// 抑制"未使用"警告
void maybe_unused_example([[maybe_unused]] int debug_param) {
[[maybe_unused]] int temp = 42;
#ifndef NDEBUG
// debug_param 和 temp 仅在 debug 模式使用
assert(debug_param > 0);
assert(temp == 42);
#endif
}
// ═══════════ [[fallthrough]] ═══════════
// 明确标记 switch 的贯穿是有意为之
void fallthrough_example(int level) {
switch (level) {
case 3:
std::cout << "Level 3 init\n";
[[fallthrough]]; // ✅ 明确表示有意贯穿,抑制警告
case 2:
std::cout << "Level 2 init\n";
[[fallthrough]];
case 1:
std::cout << "Level 1 init\n";
break;
default:
std::cout << "Unknown level\n";
}
}
/*
* C++17 属性总结:
* ┌─────────────────┬────────────────────────────────────────┐
* │ [[nodiscard]] │ 返回值不能忽略(错误码、RAII 工厂等) │
* │ [[maybe_unused]]│ 变量/参数可能未使用(抑制警告) │
* │ [[fallthrough]] │ switch 贯穿是有意的(抑制警告) │
* └─────────────────┴────────────────────────────────────────┘
*/
🧩 12. 其他语言特性
12.1 内联变量(Inline Variables):
C++
// ❌ C++14:头文件中定义全局/静态变量会导致多重定义错误
// header.h
// const int MAX = 100; // 每个 TU 各自一份
// static int counter = 0; // 每个 TU 各自一份
// ✅ C++17:inline 变量,多个 TU 共享同一个实例
// header.h
inline int globalCounter = 0; // 所有 TU 共享一份
inline const std::string appName = "MyApp";
// 最大受益者:类的 static 成员变量
struct Config {
// ❌ C++14:需要在 .cpp 文件中定义
// static const int maxRetries; // 声明
// cpp: const int Config::maxRetries = 3; // 定义
// ✅ C++17:直接在头文件中定义
static inline int maxRetries = 3;
static inline std::string logPath = "/var/log/app.log";
static inline const std::map<int, std::string> errorMessages = {
{404, "Not Found"}, {500, "Internal Error"}
};
};
// 不需要 .cpp 文件中的定义!
12.2 嵌套命名空间:
C++
// ❌ C++14
namespace Company {
namespace Project {
namespace Module {
void func() {}
}
}
}
// ✅ C++17
namespace Company::Project::Module {
void func() {}
}
// 使用
Company::Project::Module::func();
12.3 auto 非类型模板参数:
C++
// ✅ C++17:非类型模板参数可以用 auto 推导类型
template <auto Value>
struct Constant {
static constexpr auto value = Value;
using type = decltype(Value);
};
Constant<42> intConst; // Value 是 int
Constant<'A'> charConst; // Value 是 char
Constant<true> boolConst; // Value 是 bool
// 实际应用:编译期常量列表
template <auto... Values>
struct ValueList {};
using MyList = ValueList<1, 'a', true>; // 混合类型的非类型参数!
// 配合 constexpr
template <auto N>
constexpr auto factorial() {
decltype(N) result = 1;
for (decltype(N) i = 2; i <= N; ++i) result *= i;
return result;
}
static_assert(factorial<5>() == 120);
static_assert(factorial<10>() == 3628800);
12.4 constexpr lambda:
C++
// ✅ C++17:Lambda 可以是 constexpr(满足条件时自动是)
auto square = [](int x) constexpr { return x * x; };
static_assert(square(5) == 25);
constexpr auto add = [](int a, int b) { return a + b; }; // 自动 constexpr
static_assert(add(3, 4) == 7);
// 编译期使用
template <int N>
struct Array {
int data[N];
};
constexpr auto size = [](int rows, int cols) { return rows * cols; };
Array<size(3, 4)> matrix; // Array<12>
12.5 强制复制消除(Guaranteed Copy Elision):
C++
// C++17 保证在特定场景下不会调用拷贝/移动构造函数
struct NonMovable {
NonMovable() { std::cout << "Constructed\n"; }
NonMovable(const NonMovable&) = delete; // 不可拷贝
NonMovable(NonMovable&&) = delete; // 不可移动
};
NonMovable create() {
return NonMovable{}; // ✅ C++17 保证不调用拷贝/移动(直接构造在调用者的位置)
}
void guaranteed_elision() {
NonMovable obj = create(); // ✅ OK!即使拷贝/移动被 delete
// C++14 中这会编译失败(需要移动构造存在,即使可能被省略)
// 同样适用于:
NonMovable obj2 = NonMovable{}; // ✅ 直接构造,无临时对象
auto obj3 = NonMovable{}; // ✅ 同上
}
// 这意味着工厂函数可以返回不可移动的类型
std::mutex create_mutex() {
return std::mutex{}; // ✅ C++17 OK(mutex 不可拷贝不可移动)
// C++14 ❌ 编译错误
}
12.6 __has_include 预处理器:
C++
// ✅ C++17:编译期检查头文件是否可用
#if __has_include(<optional>)
#include <optional>
#define HAS_OPTIONAL 1
#elif __has_include(<experimental/optional>)
#include <experimental/optional>
#define HAS_OPTIONAL 1
namespace std { using std::experimental::optional; }
#else
#define HAS_OPTIONAL 0
#endif
#if __has_include(<filesystem>)
#include <filesystem>
#endif
📚 13. 标准库其他增强
13.1 std::invoke 与 std::apply:
C++
#include <functional>
#include <tuple>
// std::invoke:统一调用任何可调用对象
struct Foo {
int value = 42;
void print() const { std::cout << "Foo::print " << value << "\n"; }
};
void invoke_example() {
// 自由函数
auto result = std::invoke([](int a, int b) { return a + b; }, 3, 4);
// 成员函数
Foo foo;
std::invoke(&Foo::print, foo); // 调用 foo.print()
// 成员变量
int val = std::invoke(&Foo::value, foo); // 访问 foo.value
// 函数对象
std::invoke(std::plus<>{}, 10, 20); // 30
}
// std::apply:将 tuple 的元素展开为函数参数
void apply_example() {
auto args = std::make_tuple(1, 2.5, std::string("hello"));
auto print = [](int i, double d, const std::string& s) {
std::cout << i << ", " << d << ", " << s << "\n";
};
std::apply(print, args); // 等价于 print(1, 2.5, "hello")
// 实用:构造对象
auto params = std::make_tuple(10, 20);
auto point = std::make_from_tuple<std::pair<int, int>>(params);
}
13.2 数学工具:
C++
#include <numeric>
#include <cmath>
#include <algorithm>
void math_utils() {
// std::clamp:限制值在范围内
int val = 150;
int clamped = std::clamp(val, 0, 100); // 100
double d = std::clamp(3.7, 0.0, 1.0); // 1.0
// std::gcd / std::lcm:最大公约数 / 最小公倍数
std::cout << std::gcd(12, 18) << "\n"; // 6
std::cout << std::lcm(4, 6) << "\n"; // 12
// 特殊数学函数(C++17 新增)
// std::beta, std::legendre, std::hermite, std::laguerre 等
// std::riemann_zeta, std::cyl_bessel_j 等
}
// std::reduce / std::transform_reduce(见并行算法部分)
// std::inclusive_scan / std::exclusive_scan:前缀和
void scan_examples() {
std::vector<int> input = {1, 2, 3, 4, 5};
std::vector<int> output(input.size());
// inclusive_scan: [1, 3, 6, 10, 15]
std::inclusive_scan(input.begin(), input.end(), output.begin());
// exclusive_scan: [0, 1, 3, 6, 10]
std::exclusive_scan(input.begin(), input.end(), output.begin(), 0);
}
13.3 std::scoped_lock(多互斥量死锁安全锁):
C++
#include <mutex>
// ❌ C++14:手动处理多锁容易死锁
std::mutex m1, m2;
void old_multi_lock() {
std::lock(m1, m2);
std::lock_guard<std::mutex> lg1(m1, std::adopt_lock);
std::lock_guard<std::mutex> lg2(m2, std::adopt_lock);
// 3 行代码...
}
// ✅ C++17:scoped_lock 一行搞定
void new_multi_lock() {
std::scoped_lock lock(m1, m2); // CTAD + 同时锁定 + RAII
// 自动按安全顺序锁定,作用域结束自动解锁
}
// 单个锁也能用(替代 lock_guard)
void single_lock() {
std::scoped_lock lock(m1); // 等价于 lock_guard
}
13.4 std::shared_mutex(非定时读写锁):
C++
#include <shared_mutex>
// C++14 有 shared_timed_mutex,C++17 新增更轻量的 shared_mutex
class ThreadSafeData {
std::vector<int> data_;
mutable std::shared_mutex mutex_;
public:
void write(int value) {
std::unique_lock lock(mutex_); // 独占写锁
data_.push_back(value);
}
int read(size_t index) const {
std::shared_lock lock(mutex_); // 共享读锁
return data_.at(index);
}
size_t size() const {
std::shared_lock lock(mutex_);
return data_.size();
}
};
13.5 std::byte:
C++
#include <cstddef>
// std::byte:表示原始字节数据的类型安全方式
void byte_example() {
// ❌ 以前用 char 或 unsigned char,容易与字符/整数混淆
// ✅ std::byte 不支持算术操作,只支持位操作
std::byte b{0x42};
// 位操作
std::byte mask{0x0F};
auto result = b & mask; // ✅ 位与
result = b | std::byte{0x80}; // ✅ 位或
result = b ^ mask; // ✅ 位异或
result = ~b; // ✅ 位取反
result = b << 2; // ✅ 移位
// ❌ 算术操作被禁止
// auto bad = b + std::byte{1}; // 编译错误!
// auto bad = b * 2; // 编译错误!
// 与整数互转
int val = std::to_integer<int>(b); // byte → int
std::byte b2{static_cast<unsigned char>(val)}; // int → byte
}
13.6 容器改进:
C++
void container_improvements() {
// ═══ map/set 节点操作(splice)═══
std::map<int, std::string> src = {{1, "one"}, {2, "two"}, {3, "three"}};
std::map<int, std::string> dst = {{4, "four"}};
// 提取节点(不分配/释放内存!)
auto node = src.extract(2);
node.key() = 20; // ✅ 可以修改 key!
dst.insert(std::move(node));
// 合并两个 map
dst.merge(src); // src 中不冲突的元素被移到 dst
// ═══ try_emplace:避免不必要的构造 ═══
std::map<std::string, std::string> m;
// emplace:即使 key 已存在,value 也会被构造(浪费)
// try_emplace:key 已存在时不构造 value
m.try_emplace("key", "value");
m.try_emplace("key", "another"); // key 已存在,"another" 不会被构造
// ═══ insert_or_assign:插入或覆盖 ═══
m.insert_or_assign("key", "updated"); // key 存在则更新,不存在则插入
// ═══ std::map::contains (实际是 C++20,但 count() 的改进在 C++17 可用) ═══
// if (m.count("key")) // C++17 已有
// ═══ emplace_back 返回引用 ═══
std::vector<std::string> v;
auto& ref = v.emplace_back("hello"); // ✅ C++17 返回引用
ref += " world";
std::cout << v.back() << "\n"; // "hello world"
// ═══ std::size, std::empty, std::data 自由函数 ═══
int arr[] = {1, 2, 3};
std::cout << std::size(arr) << "\n"; // 3
std::cout << std::empty(arr) << "\n"; // false
auto* ptr = std::data(arr); // 等同于 &arr[0]
}
13.7 std::as_const 与 std::not_fn:
C++
#include <utility>
#include <functional>
void utility_additions() {
// std::as_const:将对象转为 const 引用
std::string s = "hello";
const auto& cs = std::as_const(s); // const string&
// 用途:在范围 for 中强制调用 const 版本的 begin()/end()
for (auto& c : std::as_const(s)) {
// c 是 const char&,不能修改
}
// std::not_fn:函数对象取反(替代已弃用的 not1/not2)
auto is_even = [](int n) { return n % 2 == 0; };
auto is_odd = std::not_fn(is_even);
std::vector<int> v = {1, 2, 3, 4, 5};
auto count = std::count_if(v.begin(), v.end(), std::not_fn(is_even));
std::cout << "Odd count: " << count << "\n"; // 3
}
13.8 std::sample:
C++
#include <algorithm>
#include <random>
void sample_example() {
std::vector<int> population = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::vector<int> sampled(3);
std::mt19937 gen{std::random_device{}()};
// 从总体中随机抽取 3 个不重复的元素
std::sample(population.begin(), population.end(),
sampled.begin(), 3, gen);
for (int v : sampled) std::cout << v << " "; // 如:2 5 8
std::cout << "\n";
}
📊 14. 综合特性速查表
C++
┌──────────────────────┬───────────────────────────────────────────────────────┐
│ 特性分类 │ 具体特性 │
├──────────────────────┼───────────────────────────────────────────────────────┤
│ 结构化绑定 │ auto [x, y] = pair/tuple/struct/array │
├──────────────────────┼───────────────────────────────────────────────────────┤
│ constexpr if │ if constexpr (condition) 编译期分支 │
├──────────────────────┼───────────────────────────────────────────────────────┤
│ 折叠表达式 │ (... op args) 一元/二元 左/右折叠 │
├──────────────────────┼───────────────────────────────────────────────────────┤
│ CTAD │ std::pair p(1, 2.0) 类模板参数自动推导 │
│ │ 推导指引 (deduction guides) │
├──────────────────────┼───────────────────────────────────────────────────────┤
│ if/switch 初始化 │ if (auto x = expr; condition) │
├──────────────────────┼───────────────────────────────────────────────────────┤
│ 内联变量 │ inline static int x = 0; 头文件中定义全局/静态变量 │
├──────────────────────┼───────────────────────────────────────────────────────┤
│ 嵌套命名空间 │ namespace A::B::C { } │
├──────────────────────┼───────────────────────────────────────────────────────┤
│ auto 非类型模板参数 │ template<auto V> struct S {}; │
├──────────────────────┼───────────────────────────────────────────────────────┤
│ constexpr lambda │ auto f = [](int x) constexpr { return x*x; }; │
├──────────────────────┼───────────────────────────────────────────────────────┤
│ 强制复制消除 │ 纯右值直接构造,不需要移动/拷贝构造函数 │
├──────────────────────┼───────────────────────────────────────────────────────┤
│ 属性 │ [[nodiscard]], [[maybe_unused]], [[fallthrough]] │
├──────────────────────┼───────────────────────────────────────────────────────┤
│ __has_include │ 编译期检查头文件是否存在 │
├──────────────────────┼───────────────────────────────────────────────────────┤
│ std::optional │ 表示"有值或无值",替代指针/哨兵值 │
├──────────────────────┼───────────────────────────────────────────────────────┤
│ std::variant │ 类型安全的联合体 + std::visit 模式匹配 │
├──────────────────────┼───────────────────────────────────────────────────────┤
│ std::any │ 可持有任意类型,类型安全的 void* │
├──────────────────────┼───────────────────────────────────────────────────────┤
│ std::string_view │ 非拥有的轻量级字符串视图 │
├──────────────────────┼───────────────────────────────────────────────────────┤
│ std::filesystem │ 跨平台文件系统操作(路径/遍历/复制/删除) │
├──────────────────────┼───────────────────────────────────────────────────────┤
│ 并行算法 │ std::execution::par/seq/par_unseq 执行策略 │
│ │ std::reduce, std::transform_reduce, scan 等 │
├──────────────────────┼───────────────────────────────────────────────────────┤
│ 并发增强 │ std::scoped_lock, std::shared_mutex │
├──────────────────────┼───────────────────────────────────────────────────────┤
│ 工具增强 │ std::invoke, std::apply, std::clamp, │
│ │ std::gcd/lcm, std::byte, std::as_const, std::not_fn │
├──────────────────────┼───────────────────────────────────────────────────────┤
│ 容器增强 │ extract/merge, try_emplace, insert_or_assign, │
│ │ emplace_back 返回引用, std::size/empty/data │
└──────────────────────┴───────────────────────────────────────────────────────┘
💡 与 C++11/14 的演进关系
C++
┌─────────────────────────────────────────────────────────────────────┐
│ C++11/14 → C++17 演进关系 │
├──────────────────────┬──────────────────────────────────────────────┤
│ C++11/14 的痛点 │ C++17 的改进 │
├──────────────────────┼──────────────────────────────────────────────┤
│ pair.first/second │ ✅ 结构化绑定 auto [k, v] = pair │
│ 访问 tuple 笨拙 │ │
├──────────────────────┼──────────────────────────────────────────────┤
│ SFINAE/标签分发复杂 │ ✅ if constexpr 编译期分支 │
├──────────────────────┼──────────────────────────────────────────────┤
│ 可变参数递归终止繁琐 │ ✅ 折叠表达式 (... + args) │
├──────────────────────┼──────────────────────────────────────────────┤
│ make_pair/make_tuple │ ✅ CTAD:pair p(1, 2.0) │
│ 辅助函数赘余 │ │
├──────────────────────┼──────────────────────────────────────────────┤
│ 无标准"可选值"类型 │ ✅ std::optional │
├──────────────────────┼──────────────────────────────────────────────┤
│ 类型不安全的 union │ ✅ std::variant + std::visit │
├──────────────────────┼──────────────────────────────────────────────┤
│ 字符串传参开销/重载 │ ✅ std::string_view(O(1) 传递) │
├──────────────────────┼──────────────────────────────────────────────┤
│ 无跨平台文件系统 API │ ✅ std::filesystem │
├──────────────────────┼──────────────────────────────────────────────┤
│ 算法只能串行 │ ✅ 并行算法 + 执行策略 │
├──────────────────────┼──────────────────────────────────────────────┤
│ 多锁管理复杂 │ ✅ std::scoped_lock │
├──────────────────────┼──────────────────────────────────────────────┤
│ static 成员需 .cpp │ ✅ inline 变量,头文件直接定义 │
│ 文件定义 │ │
├──────────────────────┼──────────────────────────────────────────────┤
│ ::type / ::value 冗长│ ✅ _t 别名模板 + _v 变量模板 正式标准化 │
├──────────────────────┼──────────────────────────────────────────────┤
│ if 前变量污染作用域 │ ✅ if (init; cond) 限制变量作用域 │
└──────────────────────┴──────────────────────────────────────────────┘
💡 关键实践原则
- 用结构化绑定提升代码可读性
- 遍历 map 时
auto [key, value]替代.first/.second - 接收多返回值时一目了然
- 遍历 map 时
- 用
if constexpr替代 SFINAE 和标签分发- 模板代码复杂度降低一个量级
- 逻辑集中在一个函数内,易于理解和维护
- 用
std::optional替代指针/哨兵值表示"可能无值"- 语义清晰、类型安全、无空指针风险
- 函数返回可能失败的结果时首选
- 用
std::variant+std::visit实现类型安全的多态- 适用于封闭类型集的场景(AST、消息、配置等)
- 配合 overloaded 惯用法实现模式匹配
- 用
std::string_view作为字符串参数类型- 零拷贝、兼容
std::string和 C 字符串 - ⚠️ 注意生命周期,不要返回指向局部 string 的 view
- 零拷贝、兼容
- 用
std::filesystem替代平台特定的文件操作 API- 跨平台、类型安全、功能完善
- 使用
error_code重载避免异常
- 利用并行算法加速数据密集型处理
- 仅需添加一个执行策略参数即可并行化
- ⚠️ 确保 Lambda 无数据竞争
- 用
inline变量在头文件中定义全局/静态数据- 彻底消除 header-only 库中的 ODR 问题
- 类的
static成员不再需要.cpp文件中的定义
总结:
C++17 是一次"全面补齐短板"的大版本更新。语言层面 ,结构化绑定让多值处理变得优雅,constexpr if 大幅简化了模板编程,折叠表达式终结了可变参数递归的样板代码,CTAD 减少了模板参数的显式书写,inline 变量解决了 header-only 的老大难问题。
标准库层面 ,optional/variant/any 三件套为值语义编程提供了完整的类型安全工具,string_view 消除了字符串传参的性能焦虑,filesystem 填补了跨平台文件操作的长期空白,并行算法让数据密集型代码"一参数并行化"成为现实。
C++17 的设计理念是"让常见操作更简单,让复杂操作成为可能"。对于项目而言,升级到 C++17 不仅能显著提升开发效率和代码质量,更重要的是,它提供的词汇类型(optional、variant、string_view)和基础设施(filesystem、并行算法)已经成为现代 C++ 项目的标配,是迈向 C++20/23 的坚实基础。