C++17 新特性

C++17 是一个大版本,引入了很多实用特性,被称为「最舒服的 C++ 版本」。


最常用的特性之一,一键解包 tuple/struct/数组:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 解包 pair
std::pair<int, std::string> getPerson() { return {25, "Tom"}; }
auto [age, name] = getPerson();

// 解包 tuple
auto [a, b, c] = std::make_tuple(1, 2.5, "hello");

// 解包 struct
struct Point { int x; int y; };
Point p{10, 20};
auto [x, y] = p;

// 解包数组
int arr[] = {1, 2, 3};
auto [a, b, c] = arr;

结合 map 遍历超好用:

1
2
3
4
std::map<int, std::string> m = {{1, "a"}, {2, "b"}};
for (const auto& [key, value] : m) {
    std::cout << key << ": " << value << std::endl;
}

实战(Q347 前 K 个高频元素):

1
2
3
4
5
6
7
8
9
// 传统写法:p.first / p.second 访问键值对
for (auto& p : u_map) {
    vec.push_back({p.second, p.first});   // p.first 是数字,p.second 是次数
}

// 结构化绑定写法:一行解包,见名知意
for (auto& [num, cnt] : u_map) {
    vec.push_back({cnt, num});
}

注意:LeetCode 默认 C++17,可以直接用 auto [num, cnt];本地老编译器(C++14 及以下)不支持。


在 if 条件里定义变量,缩小作用域:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// C++17 写法
if (auto it = m.find(key); it != m.end()) {
    // 使用 it
}
// it 在这里已经不可见了

// switch 同理
switch (int val = getValue(); val) {
    case 1: ...
    case 2: ...
}

解决了「全局变量在头文件里定义导致多重定义」的老问题:

1
2
3
4
5
6
7
// 头文件中,C++17 之前会报重复定义
inline constexpr int MAX_SIZE = 1024;

// 静态成员变量也可以直接在类内初始化
class MyClass {
    inline static int count = 0;  // C++17 不需要在 .cpp 里再定义一次
};

简化可变参数模板的展开:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 对所有参数求和
template<typename... Args>
auto sum(Args... args) {
    return (... + args);  // 一元右折叠:((a1 + a2) + a3) + ...
}

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

// 带初始值的折叠
template<typename... Args>
void printAll(Args... args) {
    (std::cout << ... << args) << std::endl;
}

四种折叠形式:

  • (... op args) 一元右折叠
  • (args op ...) 一元左折叠
  • (init op ... op args) 二元右折叠
  • (args op ... op init) 二元左折叠

C++17 中 constexpr 可以用的地方更多了:

编译期条件判断,替代部分 SFINAE 写法:

1
2
3
4
5
6
7
8
template<typename T>
auto getValue(T x) {
    if constexpr (std::is_integral_v<T>) {
        return x + 1;
    } else {
        return x;
    }
}

lambda 可以在编译期执行:

1
2
constexpr auto square = [](int n) { return n * n; };
static_assert(square(5) == 25);  // 编译期计算

构造模板类时不用写模板参数,编译器自动推导:

1
2
3
4
5
6
7
// C++11/14:必须写参数
std::pair<int, double> p1(1, 2.5);
std::vector<int> v1{1, 2, 3};

// C++17:自动推导
std::pair p2(1, 2.5);     // pair<int, double>
std::vector v2{1, 2, 3};  // vector<int>

表示「可能有值,可能为空」,替代指针判空:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
std::optional<int> findNum(int x) {
    if (x > 0) return x;
    return std::nullopt;  // 空
}

auto res = findNum(5);
if (res) {
    std::cout << *res << std::endl;
}

// 有值返回,没值返回默认
int val = findNum(-1).value_or(0);

类型安全的 union,访问时用 std::visit:

1
2
3
4
5
6
7
8
std::variant<int, double, std::string> v;
v = 42;
v = 3.14;
v = "hello";

std::visit([](auto&& arg) {
    std::cout << arg << std::endl;
}, v);

可以存任意类型的容器,类似 void* 但类型安全:

1
2
3
4
5
6
std::any a = 42;
a = std::string("hello");

if (a.type() == typeid(std::string)) {
    std::cout << std::any_cast<std::string>(a) << std::endl;
}

字符串的「视图」,不持有数据,零拷贝:

1
2
3
4
5
6
void print(std::string_view sv) {  // 传 const char* 或 std::string 都不用拷贝
    std::cout << sv << std::endl;
}

print("hello");           // OK
print(std::string("hi")); // OK

注意: string_view 不拥有数据,源字符串销毁后就悬空了。


终于有了标准的文件操作库:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <filesystem>
namespace fs = std::filesystem;

// 创建目录
fs::create_directory("test");

// 遍历目录
for (auto& entry : fs::directory_iterator(".")) {
    std::cout << entry.path() << std::endl;
}

// 文件大小、路径操作
auto size = fs::file_size("test.txt");
auto ext = fs::path("test.txt").extension();

特性说明
[[nodiscard]]标记返回值不能忽略,编译器会警告
[[maybe_unused]]标记可能不用的变量,消除警告
[[fallthrough]]switch 中显式标注故意 fall-through
十六进制浮点字面量0x1.2p3
std::as_const把左值转成 const 引用
std::clamp把值限制在区间内 clamp(val, min, max)
1
2
3
// [[nodiscard]] 示例
[[nodiscard]] int compute() { return 42; }
compute();  // 警告:返回值被忽略

C++17 是日常开发提升幸福感最多的版本:

  • 结构化绑定 + if-init:代码更简洁
  • optional/variant/any:类型安全三件套
  • string_view:字符串处理性能提升
  • filesystem:终于不用自己写文件操作了
  • constexpr if:模板元编程变得可读

相关内容