Skip to content

Ch 14: C++20/23 新特性

  • 理解 C++20 Ranges 库的惰性求值模型
  • 掌握 std::format 格式化语法
  • 了解 C++20 Modules 的优势
  • 熟悉 C++23 的新特性
  • 理解三路比较运算符 <=>

Ranges 库是 C++20 最重大的库增强之一,改变了我们处理数据的方式:

传统算法的局限性:

#include <vector>
#include <algorithm>
#include <iostream>
void traditional_algorithm_demo() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 传统方式:需要临时容器
std::vector<int> result;
for (int n : numbers) {
if (n % 2 == 0) { // 过滤偶数
result.push_back(n * n); // 平方
}
}
// 问题:
// 1. 需要额外的内存
// 2. 多次遍历需要多个容器
// 3. 代码不直观(循环比算法更明显)
// 4. 无法组合操作
for (int n : result) {
std::cout << n << " ";
}
}

Ranges 解决方案:

#include <vector>
#include <ranges>
#include <iostream>
void ranges_demo() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 惰性求值 - 不创建中间容器
auto result = numbers
| std::views::filter([](int n) { return n % 2 == 0; })
| std::views::transform([](int n) { return n * n; });
// result 是一个惰性视图,不实际计算
// 只有在迭代时才计算
for (int n : result) {
std::cout << n << " "; // 4 16 36 64 100
}
}

Ranges 的管道语法允许组合多个操作:

#include <vector>
#include <ranges>
#include <iostream>
void view_composition_demo() {
std::vector<int> data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 组合多个视图
auto result = data
| std::views::filter([](int x) { return x % 2 == 0; }) // 偶数
| std::views::transform([](int x) { return x * x; }) // 平方
| std::views::take(3); // 取前3个
// 输出: 4 16 36
for (int n : result) {
std::cout << n << " ";
}
// views::reverse - 反转
auto reversed = data | std::views::reverse;
// views::drop - 跳过前N个
auto dropped = data | std::views::drop(3);
// views::stride - 步长
auto strided = data | std::views::stride(2);
}
#include <vector>
#include <ranges>
#include <iostream>
void view_factories_demo() {
// iota - 生成序列
auto nums = std::views::iota(1, 10); // 1, 2, 3, ..., 9
// 注意:iota(1) 表示从1开始,无限序列
// iota(1, 10) 表示 [1, 10) 范围
// single - 单元素视图
auto single_view = std::views::single(42);
// empty - 空视图
auto empty_view = std::views::empty<int>;
// 使用示例:生成斐波那契
auto fibonacci = []() {
return std::views::iota(0, 10)
| std::views::transform([](int n) {
// 简化的斐波那契计算
if (n <= 1) return n;
return n * 2; // 不准确,仅示例
});
};
// 范围for循环可以直接使用
for (int i : std::views::iota(1, 6)) {
std::cout << i << " "; // 1 2 3 4 5
}
}

C++20 在 std::ranges 命名空间下提供了惰性算法:

#include <vector>
#include <ranges>
#include <iostream>
#include <algorithm>
void ranges_algorithm_demo() {
std::vector<int> v = {5, 2, 8, 1, 9, 3};
// 惰性算法(返回视图)
auto result = v | std::views::filter([](int x) { return x > 3; });
// 如果需要酂结果,可以使用 to_vector
// std::vector<int> filtered = result | std::ranges::to<std::vector>();
// std::ranges::sort(仍然需要酂结果)
std::ranges::sort(v);
std::cout << "Sorted: ";
for (int n : v) std::cout << n << " ";
std::cout << std::endl;
// std::ranges::find
auto it = std::ranges::find(v, 5);
if (it != v.end()) {
std::cout << "Found at index: " << std::ranges::distance(v.begin(), it);
}
// std::ranges::transform
std::vector<int> doubled(v.size());
std::ranges::transform(v, doubled.begin(), [](int x) { return x * 2; });
}

Python 风格的格式化库,安全且类型化:

#include <format>
#include <iostream>
#include <string>
void format_demo() {
// {} 占位符自动编号
std::string s1 = std::format("Hello, {}!", "World");
std::cout << s1 << std::endl; // Hello, World!
// {0}, {1} 手动编号
std::string s2 = std::format("{1} {0}", "World", "Hello");
std::cout << s2 << std::endl; // Hello World
// 重复使用参数
std::string s3 = std::format("{0} {0} {0}", "repeat");
std::cout << s3 << std::endl; // repeat repeat repeat
}
#include <format>
#include <iostream>
#include <iomanip>
void format_spec_demo() {
// 整数格式
std::cout << std::format("{:d}", 42) << std::endl; // 42
std::cout << std::format("{:5d}", 42) << std::endl; // " 42"
std::cout << std::format("{:05d}", 42) << std::endl; // "00042"
std::cout << std::format("{:+d}", 42) << std::endl; // "+42"
std::cout << std::format("{:x}", 255) << std::endl; // "ff"
std::cout << std::format("{:X}", 255) << std::endl; // "FF"
std::cout << std::format("{:b}", 42) << std::endl; // "101010"
std::cout << std::format("{:o}", 42) << std::endl; // "52"
// 浮点数格式
std::cout << std::format("{:f}", 3.14159) << std::endl; // "3.141590"
std::cout << std::format("{:.2f}", 3.14159) << std::endl; // "3.14"
std::cout << std::format("{:e}", 3.14159) << std::endl; // "3.141590e+00"
std::cout << std::format("{:E}", 3.14159) << std::endl; // "3.141590E+00"
std::cout << std::format("{:g}", 3.14) << std::endl; // "3.14"
std::cout << std::format("{:g}", 0.000001) << std::endl; // "1e-06"
// 字符串格式
std::cout << std::format("{:>10}", "right") << std::endl; // 右对齐
std::cout << std::format("{:<10}", "left") << std::endl; // 左对齐
std::cout << std::format("{:^10}", "center") << std::endl; // 居中
std::cout << std::format("{:10}", "pad") << std::endl; // 默认左对齐
}
#include <format>
#include <iostream>
#include <chrono>
#include <ctime>
void format_custom_demo() {
// 格式化时间
auto now = std::chrono::system_clock::now();
std::time_t t = std::chrono::system_clock::to_time_t(now);
std::tm* tm = std::localtime(&t);
std::cout << std::format("{:%Y-%m-%d %H:%M:%S}", *tm) << std::endl;
// 格式化指针
int x = 42;
std::cout << std::format("{:p}", &x) << std::endl; // 指针地址
// 布尔值
std::cout << std::format("{:d}", true) << std::endl; // "1"
std::cout << std::format("{:s}", true) << std::endl; // "true"
}

见第13章,这里补充一些实际应用:

#include <concepts>
#include <iostream>
#include <vector>
#include <list>
// 用 Concept 约束模板参数
template<std::ranges::random_access_range R>
void sort_range(R& r) {
std::ranges::sort(r);
}
// random_access_range 要求:
// - begin(), end() 返回随机访问迭代器
// - 支持 O(1) 的下标访问
// - std::vector 满足,std::list 不满足
void concept_application_demo() {
std::vector<int> v = {3, 1, 4, 1, 5, 9};
sort_range(v); // OK
std::list<int> lst = {3, 1, 4, 1, 5, 9};
// sort_range(lst); // 编译错误:list 是双向迭代器,不是随机访问
}

Modules 是 C++20 最重要的新特性之一,目标是取代头文件:

传统头文件的缺点:

old_header.h
#ifndef OLD_HEADER_H
#define OLD_HEADER_H
// 每次包含都重新解析整个文件
// 宏定义可能冲突
// 编译时间随包含层次指数增长
// 没有封装可言(内部实现暴露)
class OldStyle {
int internal_state_; // 对包含者可见
};
#endif
my_module.cppm
module; // 模块单元声明
module my_module; // 模块名称
// 导出接口
export module my_module;
export int add(int a, int b) {
return a + b;
}
export class Widget {
public:
void do_something();
private:
int private_data_; // 对导入者不可见
};
// 实现(在同一个文件或单独文件)
void Widget::do_something() {
// 实现细节
}
main.cpp
import my_module; // 导入模块
int main() {
int result = add(1, 2);
std::cout << "Result: " << result << std::endl;
Widget w;
w.do_something(); // 可以调用导出的方法
// w.private_data_; // 错误:不可访问
return 0;
}
math_utils.cppm
export module math_utils;
export namespace math {
double PI = 3.14159265358979;
export double circle_area(double radius) {
return PI * radius * radius;
}
namespace internal {
// 内部辅助函数,不导出
double辅助函数() { /* ... */ }
}
}
// main.cpp
import math_utils;
int main() {
double area = math::circle_area(5.0);
std::cout << "Area: " << area << std::endl;
// math::internal::辅助函数(); // 错误:不可访问
return 0;
}

C++20 引入了宇宙飞船运算符 <=>:

#include <compare>
#include <iostream>
void spaceship_demo() {
int a = 1, b = 2;
// <=> 返回强排序类型
auto result = a <=> b;
if (result < 0) {
std::cout << "a < b" << std::endl;
} else if (result == 0) {
std::cout << "a == b" << std::endl;
} else {
std::cout << "a > b" << std::endl;
}
// 也可以用于其他类型
std::string s1 = "apple", s2 = "banana";
auto cmp = s1 <=> s2; // 字典序比较
}
#include <compare>
#include <iostream>
class Point {
public:
int x, y;
// C++20 自动生成 ==, !=, <, >, <=, >=
auto operator<=>(const Point&) const = default;
};
void point_comparison_demo() {
Point p1{1, 2}, p2{1, 2}, p3{2, 1};
std::cout << std::boolalpha;
std::cout << "p1 == p2: " << (p1 == p2) << std::endl; // true
std::cout << "p1 != p3: " << (p1 != p3) << std::endl; // true
std::cout << "p1 < p3: " << (p1 < p3) << std::endl; // true
}

类似 Rust 的 Result 类型:

#include <expected>
#include <iostream>
#include <string>
std::expected<int, std::string> parse_int(const std::string& s) {
try {
return std::stoi(s);
}
catch (...) {
return std::unexpected("Invalid integer: " + s);
}
}
void expected_demo() {
auto r1 = parse_int("42");
if (r1) {
std::cout << "Parsed: " << *r1 << std::endl;
}
auto r2 = parse_int("abc");
if (!r2) {
std::cout << "Error: " << r2.error() << std::endl;
}
// transform 和 and_then
auto doubled = r1.transform([](int x) { return x * 2; });
}

将范围收集到容器:

#include <vector>
#include <ranges>
#include <iostream>
void ranges_to_demo() {
auto result = std::views::iota(1, 6)
| std::views::filter([](int x) { return x % 2 == 0; })
| std::views::transform([](int x) { return x * x; })
| std::ranges::to<std::vector<int>>();
// result = {4, 16}
}
#include <iostream>
void auto_deduction_demo() {
int x = 42;
int& rx = x;
const int& crx = x;
// auto 通常会移除引用和 const
auto a1 = rx; // int,不是 int&
auto a2 = crx; // int,不是 const int&
// C++23: auto(x) 保持相同类型
auto a3 = auto(x); // int
auto a4 = auto(rx); // int&(保持引用)
auto a5 = auto(crx); // const int&(保持 const)
}
// 1. import std 导入整个标准库(C++23)
import std;
import std.io;
int main() {
std::println("Hello, {}!", "World"); // 类似 Python print
}
// 2. stacktrace(C++23)
#include <stacktrace>
void foo() {
std::cout << std::stacktrace::current() << std::endl;
}
// 3. std::optional::transform 和 and_then 已稳定
// 4. std::flat_map / std::flat_set(C++23)
// 类似 Python dict,但键是有序的

检测编译器支持:

#include <iostream>
#include <version>
// 检测 C++ 版本
#if __cplusplus >= 202002L
#include <ranges>
constexpr bool cpp20_or_later = true;
#else
constexpr bool cpp20_or_later = false;
#endif
// 使用 <version> 检查特性
#ifdef __cpp_lib_ranges // Ranges 库
// 使用 Ranges
#endif
#ifdef __cpp_lib_format // std::format
// 使用 std::format
#endif
  • C++20 Ranges 库提供惰性求值的视图组合
  • 管道操作符 | 组合多个视图
  • std::format 提供类型安全的格式化
  • Concepts 约束模板参数,让错误信息更清晰
  • Modules 目标是取代头文件,减少编译依赖
  • <=> 运算符自动生成所有比较函数
  • C++23 带来 std::expected、std::flat_map 等新特性
  • 使用 <version> 和 __cpp_lib_* 宏检测特性支持

下章预告:ch15 项目迁移策略。