C++20 引入了两个重磅特性——范围(Ranges)和概念(Concepts)。这两个特性在一起使用,可以大幅提升代码的可读性、可维护性和类型安全。下面我们通过一个具体的例子来演示它们如何协同工作,并在实践中提供一些实用技巧。
1. 传统方式:std::sort + std::vector
#include <algorithm>
#include <vector>
std::vector <int> vec = {5, 2, 9, 1, 5, 6};
std::sort(vec.begin(), vec.end()); // 原始方式
虽然上述代码简洁,但在大型项目中常会出现:
- 索引错误:
begin()/end()误用导致越界。 - 类型错误:传入不支持
operator<的容器或元素。 - 重复代码:同一容器多处排序实现细节重复。
2. 使用 Ranges 让代码更直观
#include <ranges>
#include <vector>
auto vec = std::vector{5, 2, 9, 1, 5, 6};
vec | std::ranges::sort; // Ranges 语法
优点
- 链式表达:像管道一样把操作串联,语义更清晰。
- 不再手动传递迭代器:
begin()/end()被隐藏。 - 与算法组合更灵活:可以在同一链中添加
std::views::filter、std::views::transform等。
3. 概念让调用更安全
概念是对模板参数的约束。例如,std::ranges::sortable 用来约束类型是否可排序。
#include <ranges>
#include <vector>
#include <concepts>
template <std::ranges::sortable R>
void my_sort(R&& r) {
std::ranges::sort(r);
}
调用 my_sort 时,编译器会检查传入的 R 是否满足 sortable,若不满足会产生清晰的错误信息,而不是模糊的模板错误。
4. 结合 Ranges 与 Concepts 的实战案例
需求:对一个包含自定义类 Person 的容器按年龄升序排序,同时只保留年龄大于 20 的人。
#include <iostream>
#include <ranges>
#include <vector>
struct Person {
std::string name;
int age;
};
auto persons = std::vector{
Person{"Alice", 30},
Person{"Bob", 18},
Person{"Charlie", 25},
Person{"David", 19}
};
// 用 lambda 作为概念约束:仅适用于可比较且具有 age 成员
auto age_greater_than_20 = std::views::filter([](const Person& p){ return p.age > 20; });
auto sorted_by_age = persons | age_greater_than_20 | std::ranges::sort;
for (auto& p : sorted_by_age) {
std::cout << p.name << " (" << p.age << ")\n";
}
输出:
Charlie (25)
Alice (30)
解析:
std::views::filter先过滤年龄 > 20 的元素。std::ranges::sort在过滤结果上进行排序。- 由于
std::ranges::sort默认使用operator<,而Person没有定义此运算符,编译器会报错。我们可以通过显式传递比较函数:
std::ranges::sort(sorted_by_age, [](const Person& a, const Person& b){
return a.age < b.age;
});
或者为 Person 定义 operator<,实现更自然的语义。
5. 常见陷阱与解决方案
| 陷阱 | 解决方案 |
|---|---|
使用 views::transform 后忘记 to_vector() |
views 返回的是视图,若需要实际容器,使用 std::ranges::to<std::vector<>>(C++23)或手动复制。 |
| 多次排序导致副本生成 | 在链式调用中尽量保持视图链不产生副本;若需要持久化结果,使用 std::ranges::to 或 std::vector。 |
| 概念约束过宽导致编译慢 | 在模板中使用更细粒度的概念,例如 std::ranges::input_range 与 std::sortable 分开。 |
| 视图生命周期 | 视图引用外部容器,确保外部容器在视图使用期间不被销毁。 |
6. 小结
- Ranges:让算法链式化、可组合,避免手动迭代器。
- Concepts:在编译期对模板参数进行约束,提升错误信息的可读性与类型安全。
- 两者结合:既能写出简洁的代码,又能在编译时捕获潜在错误,是现代 C++ 开发的强大工具。
通过上述示例,你可以将 Ranges 与 Concepts 逐步引入项目,提升代码质量与开发效率。祝编码愉快!