C++17 新特性

C++17 新特性

C++17 的定位

C++17(ISO/IEC 14882:2017)没有 C++11 那样的革命性概念,但把日常代码的重复劳动砍掉一大截:结构化绑定、if 初始化、if constexpr、string_view、optional。被称为”给开发者用的版本”。

C++17 特性地图
图 1:C++17 特性地图——语言简化、编译期与类型、标准库新增三大板块

结构化绑定

#include <map>
#include <tuple>

// tuple / pair 解包
auto [x, y, z] = std::make_tuple(1, 2.5, "hello");

// map 遍历,省去 first / second
std::map<int, std::string> m = {{1, "one"}, {2, "two"}};
for (const auto& [key, val] : m) {
    std::cout << key << ": " << val << 'n';
}

// 结构体成员绑定
struct Point { int x; int y; };
Point p{10, 20};
auto [px, py] = p;    // 副本;想修改原对象用 auto&

默认解包出来是拷贝。遍历容器、操作 map 时写 const auto& / auto&,避免无谓拷贝。

if / switch 初始化语句

std::map<std::string, int> m = {{"Alice", 25}};
if (auto it = m.find("Alice"); it != m.end()) {
    std::cout << it->second << 'n';   // it 只在 if 块内可见
}
// it 在此处不可见,作用域不泄漏

switch (int n = rand() % 3; n) {
    case 0: break;
    default: break;
}

最常见的价值:find 之后立刻判断,不再把迭代器变量泄漏到外层作用域,也消除了”忘了判 end”的隐患。

内联变量

// header.h:头文件里定义变量,多个翻译单元包含也不冲突
inline int global_counter = 0;

class MyClass {
    inline static int count = 0;   // 类内直接初始化静态成员
};

解决头文件定义全局变量导致的 ODR 多重定义错误;类内静态成员终于可以就地初始化。

if constexpr:编译期分支

template <typename T>
auto twice(const T& v) {
    if constexpr (std::is_integral_v<T>) {
        return v * 2;        // 整数走这里
    } else {
        return v + v;        // 其他类型走这里
    }
}
// 未选中的分支不实例化——类型不支持也不会编译失败

编译期就确定走哪个分支,未选中的分支不参与模板实例化。这替代了相当一部分 SFINAE 技巧,模板代码的可读性提升明显。

折叠表达式

template <typename... Args>
auto sum(Args... args) {
    return (... + args);        // ((a + b) + c) + ...
}

template <typename... Args>
void print(Args... args) {
    (std::cout << ... << args) << 'n';   // 左折叠
}
print(1, " + ", 2.5, " = ", 3.5);

optional / variant / any

#include <optional>
#include <variant>
#include <any>

// optional:可能有值,也可能没有——替代哨兵值(-1、nullptr)
std::optional<int> find_id(const std::string& name);
if (auto id = find_id("alice")) {
    // *id
} else {
    // 查不到,语义显式
}

// variant:类型安全的 union,任一时刻只存一种类型
std::variant<int, double, std::string> v = 42;
v = "hello";
std::get<std::string>(v);   // 取回;类型不匹配抛 bad_variant_access

// any:运行时任意类型,有动态分配与类型检查开销,少用
std::any a = 42;

string_view:零拷贝只读视图

#include <string_view>

void parse(std::string_view sv) { /* 不拷贝 */ }

parse("abc");       // 字符串字面量直接传
std::string s = "abc";
parse(s);           // 隐式转换,零拷贝

两个注意点:string_view 不保证以 '' 结尾,不能当 C 字符串用;它不拥有数据,也不延长被引用字符串的生命周期——别让 view 活得比底层字符串久。

类模板参数推导(CTAD)

std::pair p{1, 2.5};          // std::pair<int, double>,模板参数省掉
std::vector v{1, 2, 3};       // std::vector<int>
std::tuple t{1, "two", 3.0};  // std::tuple<int, const char*, double>

// 注意:花括号优先匹配 initializer_list,推导可能出乎意料
auto v2 = std::vector{1, 2};  // vector<int>,两个元素

文件系统库

#include <filesystem>
namespace fs = std::filesystem;

fs::path dir = "data/logs";
fs::create_directories(dir);            // 递归创建目录

for (auto& entry : fs::directory_iterator(dir)) {
    if (entry.is_regular_file() && entry.path().extension() == ".log") {
        std::cout << entry.path() << 'n';
    }
}

跨平台路径与目录遍历进标准库,替代手写 POSIX/Windows 差异处理。

并行算法

#include <algorithm>
#include <execution>
#include <vector>

std::vector<int> v(10000000);
// 加一个执行策略参数,标准算法多线程执行
std::sort(std::execution::par, v.begin(), v.end());
// 前提:数据量足够大;lambda 内部必须线程安全,禁止写共享变量

其他补充

  • std::byte:语义化的字节类型,不做算术运算;
  • 嵌套命名空间 namespace a::b:省掉一层括号;
  • map 的 try_emplace / insert_or_assign:避免临时构造;
  • [[nodiscard]] / [[fallthrough]] 属性:编译器帮忙抓忽略返回值与漏写 break;
  • std::clamp / std::gcd / std::lcm:常用数学工具。

总结

C++17 的价值集中在三处:结构化绑定 + if 初始化让代码少写一半样板;if constexpr 让模板代码从”绕”变”直”;string_view / optional 消灭了两种最常见的性能与语义问题。如果你的项目还在 C++14,升级到 17 的收益远大于成本。

滚动至顶部