28、C++17/20新特性:结构化绑定、if constexpr、折叠表达式、协程(C++20)、模块(C++20)、范围库(C++20)

说实话,C++11之后的这几个标准,让我这个老码农感觉像换了个新工具箱。以前写模板元编程,那叫一个痛苦。现在有了这些新特性,代码写起来舒服多了。今天咱们就聊聊C++17和C++20里几个让我眼前一亮的东西。

核心要点:结构化绑定让解包更优雅,if constexpr让模板代码更清晰,折叠表达式让可变参数模板更简洁,协程让异步编程更自然,模块让编译更快,范围库让算法更直观。

28.1 结构化绑定:解包的艺术

我记得以前从函数返回多个值,要么用tuple,要么传引用参数。取出来还得用std::get,代码又臭又长。结构化绑定一出,整个世界清爽了。

// 以前的方式
std::tuple<int, double, std::string> getData() {
    return {42, 3.14, "hello"};
}

auto data = getData();
int a = std::get<0>(data);
double b = std::get<1>(data);
std::string c = std::get<2>(data);

// 结构化绑定
auto [a, b, c] = getData();  // 一行搞定

我在项目中经常用这个来处理map的插入结果:

std::map<std::string, int> scores;
auto [iter, inserted] = scores.insert({"Alice", 95});
if (inserted) {
    std::cout << "插入成功" << std::endl;
}

小技巧:结构化绑定还可以用在数组和结构体上。const auto& 可以避免拷贝,我建议在不需要修改时尽量用引用。

28.2 if constexpr:编译期分支

这个特性我盼了好多年。以前写模板,想根据类型做不同处理,得用SFINAE或者标签分发,代码绕来绕去。if constexpr直接让代码像普通if一样自然。

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 * 2.0;  // 浮点类型
    } else {
        return value;  // 其他类型
    }
}

我曾经在写序列化库时,用if constexpr处理不同类型,代码量直接减少了一半。注意,这里的分支是在编译期决定的,不会生成多余的代码。

注意:if constexpr的条件必须是常量表达式。另外,被丢弃的分支虽然不实例化,但语法上必须正确。

28.3 折叠表达式:变参模板的利器

处理可变参数模板时,以前得用递归展开,写起来很麻烦。折叠表达式让这一切变得简单。说白了,就是把参数包用操作符折叠起来。

// 求和
template <typename... Args>
auto sum(Args... args) {
    return (args + ...);  // 一元右折叠
}

// 打印所有参数
template <typename... Args>
void printAll(Args... args) {
    (std::cout << ... << args) << std::endl;  // 二元左折叠
}

// 检查是否都满足条件
template <typename... Args>
bool allTrue(Args... args) {
    return (args && ...);
}

我个人习惯用折叠表达式来处理日志输出,比递归模板清晰多了。你想想看,以前要写好几层的模板特化,现在一行搞定。

28.4 协程(C++20):异步编程新范式

协程是C++20的重磅特性。它让异步代码写起来像同步代码一样直观。我在做网络库时试过,确实好用。

#include <coroutine>
#include <iostream>

struct Task {
    struct promise_type {
        Task get_return_object() { return {}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() {}
    };
};

Task asyncFunction() {
    std::cout << "开始执行" << std::endl;
    co_await std::suspend_always{};  // 挂起
    std::cout << "恢复执行" << std::endl;
    co_return;
}

核心概念:co_await用于挂起,co_return用于返回,co_yield用于生成值。协程框架需要自己实现promise_type,标准库提供了基础支持。

嗯,这里要注意,协程的编译器和标准库支持还不完全一致。我在项目中用的时候,发现MSVC支持最好,GCC和Clang还在追赶。

28.5 模块(C++20):告别头文件地狱

模块是C++20对编译模型的重大改进。以前写大项目,头文件包含关系复杂,编译慢得要命。模块可以显著改善编译速度。

// math.ixx 模块接口文件
export module math;

export int add(int a, int b) {
    return a + b;
}

export namespace Math {
    int multiply(int a, int b) {
        return a * b;
    }
}

// main.cpp 使用模块
import math;

int main() {
    std::cout << add(3, 4) << std::endl;  // 7
    std::cout << Math::multiply(3, 4) << std::endl;  // 12
}

建议:模块的编译需要编译器支持。我建议在新项目中尝试,老项目迁移成本较高。目前CMake已经支持模块编译了。

28.6 范围库(C++20):算法的新写法

范围库让STL算法用起来更自然。以前写算法,要传两个迭代器,现在直接传范围就行。还支持管道操作符,链式调用很爽。

#include <ranges>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    // 传统写法
    auto even = nums | std::views::filter([](int n) { return n % 2 == 0; });
    auto squared = even | std::views::transform([](int n) { return n * n; });
    
    for (int n : squared) {
        std::cout << n << " ";  // 4 16 36 64 100
    }
    
    // 还可以用视图适配器
    auto result = nums 
        | std::views::filter([](int n) { return n > 5; })
        | std::views::take(3);
    
    for (int n : result) {
        std::cout << n << " ";  // 6 7 8
    }
}

我在处理数据管道时特别喜欢用范围库。以前要写循环加条件判断,现在一行管道就搞定了。代码的可读性提升了一个档次。

知识体系总览

C++17/20 新特性知识体系 C++17/20 新特性 结构化绑定 if constexpr 折叠表达式 协程 (C++20) 模块 (C++20) 范围库 (C++20) 应用场景 模板元编程 异步编程 编译优化 数据处理

总结对比

特性 版本 主要用途 我的评价
结构化绑定 C++17 解包tuple、pair、结构体 日常必备,代码更简洁
if constexpr C++17 编译期条件分支 模板编程的救星
折叠表达式 C++17 可变参数模板简化 递归展开的终结者
协程 C++20 异步编程 潜力巨大,生态待完善
模块 C++20 编译加速 未来趋势,值得关注
范围库 C++20 算法链式调用 代码可读性大幅提升

兼容性提醒:C++20的特性在不同编译器上的支持程度不同。我建议在项目中使用前,先确认目标编译器的支持情况。特别是协程和模块,目前还在完善中。

这些新特性让C++变得更现代、更易用。我个人觉得,C++17的特性已经非常成熟,可以放心在生产环境中使用。C++20的特性虽然强大,但建议先在非核心模块中尝试,积累经验后再全面推广。

公众号:蓝海资料掘金营,微信deep3321