在 C++ 语言的漫长历史中,C++20 是一次重要的里程碑。它不仅带来了对语言语义的细化,还引入了两项极具影响力的新特性:模块(Modules) 和 协程(Coroutines)。这两项技术为现代 C++ 程序员提供了更高效的代码组织和更强大的异步编程能力。本文将从概念、实现细节以及实际应用场景三方面,帮助你快速上手。
一、模块(Modules)——打破传统头文件的束缚
1.1 模块的基本概念
传统 C++ 使用头文件(*.h/*.hpp)来声明接口,然后在源文件(*.cpp)中实现。编译器在预处理阶段需要把头文件插入源文件,导致每个编译单元都重新编译相同内容,产生冗余。
模块通过 module 关键字定义一个“单元”,在模块接口文件(*.ixx)中声明接口,在模块实现文件(*.ixx)中实现实现细节。编译器将接口单独编译成二进制模块,其他源文件仅需引用该模块,无需再次解析头文件。
1.2 写一个简单模块
// math.ixx
export module math; // 模块名称
export interface {
int add(int a, int b);
int subtract(int a, int b);
}
// math_impl.ixx
module math; // 导入模块
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
// main.cpp
import math; // 引入模块
#include <iostream>
int main() {
std::cout << "add: " << add(3, 4) << '\n';
std::cout << "sub: " << subtract(7, 2) << '\n';
}
1.3 编译与链接
# 先编译模块接口
g++ -std=c++20 -fmodules-ts -c math.ixx -o math.o
# 编译实现
g++ -std=c++20 -fmodules-ts -c math_impl.ixx -o math_impl.o
# 链接
g++ -std=c++20 -fmodules-ts main.cpp math.o math_impl.o -o demo
提示:不同编译器对模块的支持细节不同,GCC 11+、Clang 14+、MSVC 19.29+均已实现基本功能。
1.4 模块的优势
- 编译速度:模块接口被缓存,后续编译只需加载二进制模块,无需重复解析头文件。
- 隐私:模块默认不暴露实现细节,只公开
export的接口,增强封装。 - 命名空间清晰:模块内所有符号属于模块作用域,避免全局符号冲突。
二、协程(Coroutines)——实现轻量级异步
2.1 协程的核心概念
协程是一种可挂起的函数,能够在执行过程中暂停并恢复,而不需要线程切换。C++20 在语言层面引入了 co_await, co_yield, co_return 关键字,配合 std::experimental::coroutine(或 std::coroutine 在未来标准)实现。
2.2 设计一个简单协程
#include <iostream>
#include <coroutine>
#include <optional>
template<typename T>
struct Generator {
struct promise_type;
using handle_type = std::coroutine_handle <promise_type>;
struct promise_type {
T current_value;
std::optional <T> value() { return current_value; }
Generator get_return_object() {
return Generator{handle_type::from_promise(*this)};
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
std::suspend_always yield_value(T v) {
current_value = v;
return {};
}
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
handle_type coro;
explicit Generator(handle_type h) : coro(h) {}
~Generator() { if (coro) coro.destroy(); }
T next() {
coro.resume();
return coro.promise().current_value;
}
};
Generator <int> fibonacci(int n) {
int a = 0, b = 1;
for (int i = 0; i < n; ++i) {
co_yield a;
std::tie(a, b) = std::make_pair(b, a + b);
}
}
int main() {
auto fib = fibonacci(10);
for (int i = 0; i < 10; ++i) {
std::cout << fib.next() << " ";
}
}
2.3 协程的优势
- 轻量级:协程在栈上维护状态,切换成本低于线程。
- 可组合:协程可以在异步框架中链式调用,实现复杂异步流。
- 语义清晰:
co_await等关键字直观表达“等待某个异步事件”。
2.4 与传统线程/任务的区别
| 特点 | 线程 | 协程 |
|---|---|---|
| 开销 | 栈分配、上下文切换 | 微小状态保存 |
| 并发度 | 受物理/逻辑核心限制 | 理论上无限 |
| 错误传播 | 异常可捕获 | 需要手动传递错误状态 |
| 调试 | 复杂 | 直观 |
三、实战:模块化协程 HTTP 服务器
下面给出一个简化示例,展示如何将模块与协程结合,构建异步 HTTP 服务器(仅演示核心逻辑,省略完整网络栈细节)。
// net.ixx
export module net;
export interface {
struct async_read;
struct async_write;
async_read read(int fd);
async_write write(int fd, const std::string& data);
}
// net_impl.ixx
module net;
#include <coroutine>
#include <unistd.h>
#include <string>
struct net::async_read {
struct promise_type {
std::coroutine_handle<> continuation;
int fd;
std::string buffer;
async_read get_return_object() {
return async_read{std::coroutine_handle <promise_type>::from_promise(*this)};
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
std::suspend_always yield_value(std::string&& data) {
buffer = std::move(data);
continuation.resume();
return {};
}
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
std::coroutine_handle <promise_type> coro;
std::string result() { return coro.promise().buffer; }
};
async_read net::read(int fd) {
char buf[1024];
ssize_t n = ::read(fd, buf, sizeof(buf));
if (n > 0) {
co_yield std::string(buf, n);
}
}
struct net::async_write {
// 类似 async_read,简化实现略...
};
// server.cpp
import net;
#include <iostream>
Generator <int> handle_request(int client_fd) {
auto reader = net::read(client_fd);
std::string req = reader.result();
std::cout << "收到请求: " << req << '\n';
// 简化:直接返回 200 OK
net::async_write writer = net::write(client_fd, "HTTP/1.1 200 OK\r\n\r\nHello");
co_yield 0;
}
int main() {
int listen_fd = /* 创建并绑定 socket */;
while (true) {
int client_fd = accept(listen_fd, nullptr, nullptr);
auto session = handle_request(client_fd);
// 这里可将 session 交给事件循环,使用协程调度
}
}
注意:上述代码为概念演示,真实环境需结合事件循环(如 libuv、asio)与错误处理。
四、学习资源与下一步
- 官方标准草案:阅读 C++20 Standard 了解模块与协程细节。
- 编译器实现:GCC 12+、Clang 15+、MSVC 19.32+ 均支持模块与协程,实验不同编译器的编译选项。
- 实践项目:尝试用模块化方式重构一个现有大型 C++ 项目;实现基于协程的异步 I/O 库。
结语:从 C++14 到 C++20,模块化与协程让 C++ 更加现代化。掌握它们后,你可以编写更快、更可维护、更高性能的代码。下一步不妨尝试在自己的项目中引入模块,或者用协程实现一个异步任务调度器,亲身体验这两项技术带来的变化。祝你编码愉快!