C++学习:六个月从基础到就业——C++20:范围(Ranges)进阶
本文是我C++学习之旅系列的第五十二篇技术文章,也是第三阶段"现代C++特性"的第十四篇,深入探讨C++20范围(Ranges)库的高级特性。本文承接上一篇关于Ranges基础的内容,进一步探索更复杂的用法。查看完整系列目录了解更多内容。
引言
在上一篇文章中,我们介绍了C++20范围库的基础知识,包括范围概念、视图基础和管道操作等。本文将更进一步,探索范围库的高级特性和使用技巧,包括复杂视图组合、自定义视图、性能优化策略以及与其他C++20特性的结合使用。
通过本文,你将学习如何构建更复杂、更强大的数据处理管道,创建自己的视图适配器,并优化Ranges代码的性能。无论是处理复杂数据转换还是设计流式处理库,这些高级技术都将极大提升你的C++编程能力。
目录
- 引言
- 复杂视图组合
- 自定义视图
- 性能优化
- 与其他C++20特性的结合
- 实际应用案例
- 调试与故障排除
- Ranges最佳实践
- 总结
复杂视图组合
深度嵌套视图
处理复杂的嵌套数据结构是Ranges库的一个强大用例。考虑一个学校-班级-学生的嵌套结构:
#include <ranges>
#include <vector>
#include <string>
#include <iostream>struct Student {std::string name;int score;
};struct Class {std::string name;std::vector<Student> students;
};struct School {std::string name;std::vector<Class> classes;
};void nested_view_example() {std::vector<School> schools = {{"第一中学", {{"高一(1)班", {{"张三", 85}, {"李四", 92}, {"王五", 78}}},{"高一(2)班", {{"赵六", 90}, {"钱七", 86}, {"孙八", 79}}}}},{"第二中学", {{"高一(1)班", {{"周九", 95}, {"吴十", 89}, {"郑十一", 82}}},{"高一(2)班", {{"冯十二", 88}, {"陈十三", 75}, {"褚十四", 93}}}}}};// 创建复杂的嵌套视图:查找所有90分以上的学生auto high_scorers = schools| std::views::transform([](const School& school) { return school.classes; })| std::views::join // 展平学校->班级| std::views::transform([](const Class& cls) { return cls.students; })| std::views::join // 展平班级->学生| std::views::filter([](const Student& student) { return student.score >= 90; })| std::views::transform([](const Student& student) { return student.name; });// 输出结果std::cout << "90分以上的学生:" << std::endl;for (const auto& name : high_scorers) {std::cout << "- " << name << std::endl;}
}
这个例子展示了如何使用join
视图展平嵌套数据结构,并结合transform
和filter
视图创建复杂的数据处理管道。
条件组合
有时我们需要根据不同条件应用不同的视图组合:
#include <ranges>
#include <vector>
#include <iostream>enum class FilterType { None, Even, Odd, Prime };bool is_prime(int n) {if (n <= 1) return false;if (n <= 3) return true;if (n % 2 == 0 || n % 3 == 0) return false;for (int i = 5; i * i <= n; i += 6) {if (n % i == 0 || n % (i + 2) == 0) return false;}return true;
}// 根据条件创建不同的视图组合
auto create_filtered_view(const std::vector<int>& numbers, FilterType filter_type) {switch (filter_type) {case FilterType::Even:return numbers | std::views::filter([](int n) { return n % 2 == 0; });case FilterType::Odd:return numbers | std::views::filter([](int n) { return n % 2 != 0; });case FilterType::Prime:return numbers | std::views::filter([](int n) { return is_prime(n); });case FilterType::None:default:return std::views::all(numbers);}
}
此模式允许根据运行时条件动态选择合适的视图组合,对于构建灵活的数据处理系统非常有用。
递归视图
虽然C++20 Ranges库本身不直接支持递归视图,但我们可以结合传统方法处理树形结构:
#include <ranges>
#include <vector>
#include <memory>
#include <iostream>struct TreeNode {int value;std::vector<std::shared_ptr<TreeNode>> children;TreeNode(int val) : value(val) {}void add_child(int child_value) {children.push_back(std::make_shared<TreeNode>(child_value));}
};// 递归遍历树的所有节点
std::vector<int> flatten_tree(const TreeNode& node) {std::vector<int> result = {node.value};for (const auto& child : node.children) {auto child_values = flatten_tree(*child);result.insert(result.end(), child_values.begin(), child_values.end());}return result;
}// 使用Ranges风格处理展平后的树
void process_tree(const TreeNode& root) {// 先展平树结构auto flattened = flatten_tree(root);// 使用Ranges处理auto even_squares = flattened | std::views::filter([](int n) { return n % 2 == 0; })| std::views::transform([](int n) { return n * n; });std::cout << "树中偶数节点的平方值: ";for (int value : even_squares) {std::cout << value << " ";}std::cout << std::endl;
}
这种方法虽然不是纯粹的Ranges风格,但展示了如何将传统递归与Ranges结合使用。
自定义视图
视图适配器设计
创建自定义视图需要理解三个关键组件:
- 视图类:实现范围接口的具体视图
- 视图适配器:创建视图的函数对象
- 适配器工厂:提供用户友好的接口
下面我们实现一个"步进"视图,以指定步长取元素:
#include <ranges>
#include <vector>
#include <iostream>
#include <cassert>namespace detail {// 步进视图 - 每隔n个元素取一个template<std::ranges::input_range V>class stride_view : public std::ranges::view_interface<stride_view<V>> {private:V base_ = V();std::size_t stride_ = 1;public:stride_view() = default;stride_view(V base, std::size_t stride): base_(std::move(base)), stride_(stride) {assert(stride > 0);}// 迭代器类实现class iterator {// ... 迭代器实现细节 ...// 包含current_、end_指针和stride_步长// 实现operator++, operator*, operator==等};auto begin() {return iterator{std::ranges::begin(base_), std::ranges::end(base_), stride_};}auto end() {return iterator{std::ranges::end(base_), std::ranges::end(base_), stride_};}};// 适配器函数对象struct stride_fn {template<std::ranges::input_range R>auto operator()(R&& r, std::size_t stride) const {return stride_view<std::views::all_t<R>>(std::views::all(std::forward<R>(r)), stride);}// 支持管道语法 range | views::stride(n)constexpr auto operator()(std::size_t stride) const {return [stride](auto&& r) {return stride_view<std::views::all_t<decltype(r)>>(std::views::all(std::forward<decltype(r)>(r)), stride);};}};
}namespace views {inline constexpr detail::stride_fn stride{};
}void use_stride_view() {std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};// 使用自定义stride视图auto every_second = views::stride(numbers, 2);std::cout << "每隔一个元素: ";for (int n : every_second) {std::cout << n << " "; // 1 3 5 7 9}std::cout << std::endl;// 使用管道语法auto every_third = numbers | views::stride(3);std::cout << "每隔两个元素: ";for (int n : every_third) {std::cout << n << " "; // 1 4 7 10}std::cout << std::endl;
}
通过这种方式,我们可以扩展Ranges库,实现自定义的视图适配器。
循环视图示例
另一个有用的自定义视图是循环视图,它无限重复一个范围的元素:
#include <ranges>
#include <vector>
#include <iostream>namespace detail {// 循环视图 - 无限重复一个范围的元素template<std::ranges::forward_range V>requires std::ranges::view<V>class cycle_view : public std::ranges::view_interface<cycle_view<V>> {private:V base_ = V();public:cycle_view() = default;explicit cycle_view(V base) : base_(std::move(base)) {}// 迭代器实现(简化版本)// 在达到end()时跳回begin()auto begin() {// 返回指向基础范围开始的迭代器// 实际实现需要包装这个迭代器,在到达end时重置为begin}// 此视图无限,使用特殊哨兵auto end() {return std::unreachable_sentinel;}};// 适配器函数对象struct cycle_fn {template<std::ranges::forward_range R>auto operator()(R&& r) const {return cycle_view<std::views::all_t<R>>(std::views::all(std::forward<R>(r)));}};
}namespace views {inline constexpr detail::cycle_fn cycle{};
}void use_cycle_view() {std::vector<int> numbers = {1, 2, 3};// 使用循环视图与take视图结合std::cout << "循环显示10个元素: ";for (int n : views::cycle(numbers) | std::views::take(10)) {std::cout << n << " "; // 1 2 3 1 2 3 1 2 3 1}std::cout << std::endl;
}
这个示例展示了如何创建无限视图,它必须与其他能限制元素数量的视图(如take
)结合使用。
性能优化
避免重复计算
Ranges视图的惰性特性有时会导致重复计算,特别是多次遍历同一视图时:
#include <ranges>
#include <vector>
#include <iostream>
#include <chrono>// 一个高成本的操作
int expensive_operation(int x) {// 模拟耗时操作std::this_thread::sleep_for(std::chrono::milliseconds(1));return x * x;
}void demonstrate_materialization() {std::vector<int> numbers = {1, 2, 3, 4, 5};// 创建包含昂贵操作的视图auto expensive_view = numbers | std::views::transform([](int n) {std::cout << "计算 " << n << " 的平方" << std::endl;return expensive_operation(n);});// 第一次遍历执行计算int sum1 = 0;for (int n : expensive_view) sum1 += n;// 第二次遍历再次执行相同计算int sum2 = 0;for (int n : expensive_view) sum2 += n;// 避免重复计算:具体化结果std::vector<int> materialized(expensive_view.begin(), expensive_view.end());// 使用具体化的结果多次遍历不会重复计算int sum3 = 0;for (int n : materialized) sum3 += n;int sum4 = 0;for (int n : materialized) sum4 += n;std::cout << "所有计算结果相同: " << (sum1 == sum2 && sum2 == sum3 && sum3 == sum4) << std::endl;
}
当视图操作成本高昂或需要多次遍历时,将结果具体化为容器能显著提高性能。
早期终止与短路求值
Ranges的惰性求值特性使得早期终止处理非常高效:
#include <ranges>
#include <vector>
#include <iostream>void demonstrate_early_termination() {// 创建大数据集std::vector<int> large_data(10'000'000);for (int i = 0; i < large_data.size(); ++i) {large_data[i] = i;}// 使用take实现短路求值auto first_five_even_squares = large_data| std::views::filter([](int n) { // 即使数据集很大,此函数最多只会被调用10次左右return n % 2 == 0; })| std::views::transform([](int n) { // 同样,此函数最多只会被调用5次return n * n; })| std::views::take(5); // 只取前5个结果std::cout << "首个5个偶数的平方: ";for (int n : first_five_even_squares) {std::cout << n << " "; // 0 4 16 36 64}std::cout << std::endl;
}
通过take
视图结合惰性求值,我们可以高效处理大数据集,只计算实际需要的元素。
视图具体化策略
不同场景下,选择合适的具体化策略很重要:
#include <ranges>
#include <vector>
#include <deque>
#include <list>
#include <set>
#include <iostream>template<typename Container, typename View>
auto materialize_to(View&& view) {return Container(std::ranges::begin(view), std::ranges::end(view));
}void demonstrate_materialization_strategies() {std::vector<int> data = {5, 3, 1, 4, 2};auto filtered = data | std::views::filter([](int n) { return n % 2 == 1; });// 不同的具体化策略auto vec = materialize_to<std::vector<int>>(filtered); // 适合随机访问auto deq = materialize_to<std::deque<int>>(filtered); // 适合两端操作auto lst = materialize_to<std::list<int>>(filtered); // 适合频繁插入/删除auto st = materialize_to<std::set<int>>(filtered); // 适合排序/去重// 在C++23中,可以使用to<Container>()适配器// auto vec2 = filtered | std::ranges::to<std::vector<int>>();
}
根据后续操作选择合适的容器类型进行具体化,可以优化性能。
与其他C++20特性的结合
Ranges与概念
Ranges库和概念(Concepts)紧密结合,可以创建类型安全的数据处理函数:
#include <ranges>
#include <vector>
#include <iostream>
#include <type_traits>// 使用概念定义约束
template<typename T>
concept Numeric = std::is_arithmetic_v<T>;template<typename R>
concept NumericRange = std::ranges::input_range<R> && Numeric<std::ranges::range_value_t<R>>;// 使用概念约束的范围函数
template<NumericRange R>
auto average(const R& range) {using ValueType = std::ranges::range_value_t<R>;ValueType sum{};std::size_t count = 0;for (const auto& value : range) {sum += value;++count;}return count > 0 ? sum / static_cast<ValueType>(count) : ValueType{};
}void ranges_with_concepts() {std::vector<double> values = {1.5, 2.5, 3.5, 4.5, 5.5};// 计算全部数据的平均值std::cout << "平均值: " << average(values) << std::endl;// 计算满足条件数据的平均值auto filtered = values | std::views::filter([](double d) { return d > 3.0; });std::cout << "大于3的值的平均值: " << average(filtered) << std::endl;// 这里会编译失败,因为字符串不满足NumericRange约束// std::vector<std::string> strings = {"a", "b", "c"};// average(strings);
}
结合范围和概念,可以创建类型安全、语义清晰的通用数据处理函数。
Ranges与协程
Ranges可以与协程(Coroutines)结合,处理异步数据流:
#include <ranges>
#include <vector>
#include <iostream>
#include <future>
#include <thread>// 异步获取数据
std::future<std::vector<int>> fetch_data_async(int count) {return std::async(std::launch::async, [count]() {std::vector<int> result;// 模拟异步处理std::this_thread::sleep_for(std::chrono::milliseconds(500));for (int i = 0; i < count; ++i) {result.push_back(i);}return result;});
}// 使用Ranges处理异步数据
void process_async_data() {auto future = fetch_data_async(100);// 等待数据完成auto data = future.get();// 使用Ranges处理数据auto processed = data| std::views::filter([](int n) { return n % 2 == 0; })| std::views::transform([](int n) { return n * n; })| std::views::take(5);std::cout << "处理结果: ";for (int n : processed) {std::cout << n << " "; // 0 4 16 36 64}std::cout << std::endl;
}
随着C++20协程的普及,这种结合将使异步数据处理更加简洁高效。
实际应用案例
文本处理引擎
使用Ranges库构建一个简单的文本处理引擎:
#include <ranges>
#include <vector>
#include <string>
#include <iostream>
#include <regex>
#include <map>
#include <algorithm>class TextProcessor {
private:std::vector<std::string> lines_;public:// 从文本设置行void set_lines(std::vector<std::string> lines) {lines_ = std::move(lines);}// 过滤包含特定子串的行auto lines_containing(const std::string& substr) const {return lines_ | std::views::filter([&substr](const std::string& line) {return line.find(substr) != std::string::npos;});}// 过滤匹配正则表达式的行auto lines_matching(const std::regex& pattern) const {return lines_ | std::views::filter([&pattern](const std::string& line) {return std::regex_search(line, pattern);});}// 应用转换函数到每一行auto transform_lines(auto transformer) const {return lines_ | std::views::transform(transformer);}// 统计单词频率std::map<std::string, int> word_frequency() const {std::map<std::string, int> frequencies;std::regex word_pattern(R"(\b\w+\b)");for (const auto& line : lines_) {auto words_begin = std::sregex_iterator(line.begin(), line.end(), word_pattern);auto words_end = std::sregex_iterator();for (auto it = words_begin; it != words_end; ++it) {std::string word = it->str();std::transform(word.begin(), word.end(), word.begin(), [](unsigned char c) { return std::tolower(c); });frequencies[word]++;}}return frequencies;}// 获取前N个最常用词auto top_words(int n) const {auto freqs = word_frequency();std::vector<std::pair<std::string, int>> word_pairs(freqs.begin(), freqs.end());std::ranges::sort(word_pairs, std::ranges::greater{}, &std::pair<std::string, int>::second);return word_pairs | std::views::take(n);}
};void text_processor_example() {TextProcessor processor;processor.set_lines({"The quick brown fox jumps over the lazy dog.","C++ Ranges library provides functional-style operations on sequences.","The C++20 standard introduces many new features including concepts and coroutines.","Ranges allow for lazy evaluation and composition of operations."});// 查找包含"fox"的行std::cout << "包含'fox'的行:" << std::endl;for (const auto& line : processor.lines_containing("fox")) {std::cout << line << std::endl;}// 使用正则表达式查找所有包含"C++"的行std::regex cpp_pattern(R"(C\+\+)");std::cout << "\n包含'C++'的行:" << std::endl;for (const auto& line : processor.lines_matching(cpp_pattern)) {std::cout << line << std::endl;}// 转换所有行为大写std::cout << "\n转换为大写:" << std::endl;for (const auto& line : processor.transform_lines([](const std::string& line) {std::string upper = line;std::transform(upper.begin(), upper.end(), upper.begin(), ::toupper);return upper;})) {std::cout << line << std::endl;}// 获取最常见的3个单词std::cout << "\n最常见的3个单词:" << std::endl;for (const auto& [word, count] : processor.top_words(3)) {std::cout << word << ": " << count << "次" << std::endl;}
}
这个示例展示了如何使用Ranges库构建功能丰富的文本处理工具,结合视图和范围算法进行各种文本操作。
数据分析示例
使用Ranges库简化数据分析任务:
#include <ranges>
#include <vector>
#include <string>
#include <iostream>
#include <numeric>
#include <iomanip>struct DataPoint {std::string category;double value;
};class DataAnalyzer {
private:std::vector<DataPoint> data_;public:void add_data(std::vector<DataPoint> data) {data_.insert(data_.end(), data.begin(), data.end());}// 按类别过滤auto filter_by_category(const std::string& category) const {return data_ | std::views::filter([&category](const DataPoint& dp) {return dp.category == category;});}// 计算平均值double average(auto range) const {double sum = 0.0;int count = 0;for (const auto& dp : range) {sum += dp.value;++count;}return count > 0 ? sum / count : 0.0;}// 找出最大值auto max_value(auto range) const {return std::ranges::max_element(range, {}, &DataPoint::value);}// 按值排序auto sorted_by_value(auto range) const {auto copied = std::vector<DataPoint>(range.begin(), range.end());std::ranges::sort(copied, {}, &DataPoint::value);return copied;}// 生成报告void generate_report() const {// 获取所有唯一类别std::vector<std::string> categories;auto category_view = data_ | std::views::transform(&DataPoint::category);for (const auto& cat : category_view) {if (std::find(categories.begin(), categories.end(), cat) == categories.end()) {categories.push_back(cat);}}// 为每个类别生成统计std::cout << std::left << std::setw(15) << "类别" << std::setw(10) << "数量" << std::setw(10) << "平均值" << std::setw(10) << "最大值" << std::endl;std::cout << std::string(45, '-') << std::endl;for (const auto& category : categories) {auto category_data = filter_by_category(category);auto count = std::ranges::distance(category_data);auto avg = average(category_data);auto max_it = max_value(category_data);auto max = max_it != data_.end() ? max_it->value : 0.0;std::cout << std::left << std::setw(15) << category << std::setw(10) << count << std::setw(10) << std::fixed << std::setprecision(2) << avg << std::setw(10) << max << std::endl;}}
};void data_analysis_example() {DataAnalyzer analyzer;// 添加样本数据analyzer.add_data({{"电子", 230.50}, {"服装", 120.75}, {"食品", 45.30},{"电子", 180.99}, {"服装", 85.50}, {"食品", 32.99},{"电子", 320.00}, {"服装", 95.75}, {"食品", 50.25},{"电子", 195.50}, {"服装", 110.25}, {"食品", 37.50}});// 生成分析报告analyzer.generate_report();// 使用视图组合查找特定数据auto expensive_electronics = analyzer.filter_by_category("电子")| std::views::filter([](const DataPoint& dp) { return dp.value > 200.0; });std::cout << "\n高价电子产品:" << std::endl;for (const auto& item : expensive_electronics) {std::cout << "价格: " << item.value << std::endl;}
}
这个示例展示了如何使用Ranges库构建基本的数据分析工具,包括过滤、排序和聚合操作。
调试与故障排除
常见错误模式
使用Ranges库时常见的错误:
- 悬空引用:视图通常引用原始数据,必须确保引用对象的生命周期
// 危险代码
auto get_filtered() {std::vector<int> temp = {1, 2, 3, 4, 5};return temp | std::views::filter([](int n) { return n % 2 == 0; });
} // temp超出作用域,视图引用失效
- 共享状态:lambda捕获的变量在迭代过程中可能会改变
std::vector<int> numbers = {1, 2, 3, 4, 5};
int threshold = 3;// threshold在lambda中被捕获,如果外部修改threshold,会影响过滤结果
auto above_threshold = numbers | std::views::filter([&threshold](int n) { return n > threshold;
});// 遍历过程中修改阈值
for (int n : above_threshold) {std::cout << n << " ";threshold++; // 这会改变过滤条件!
}
- 未预期的惰性计算:视图操作在需要结果时才执行
std::vector<int> data = {1, 2, 3, 4, 5};// 这里没有实际计算任何东西
auto view = data | std::views::transform([](int n) { std::cout << "计算" << n << "的平方\n"; return n * n;
});std::cout << "视图创建完成\n";
// 只有在这里才开始实际计算
for (int n : view) {std::cout << "结果: " << n << "\n";
}
调试技巧
调试Ranges代码的几个有用技巧:
- 具体化中间结果:将视图转换为容器以便检查
auto view1 = data | std::views::filter(...);
// 调试检查
std::vector<int> debug_copy(view1.begin(), view1.end());
for (auto x : debug_copy) std::cout << x << " ";
- 添加打印语句:在lambda中添加打印语句跟踪执行流程
auto debugged_view = data | std::views::transform([](int n) {std::cout << "处理值: " << n << std::endl;auto result = process(n);std::cout << "结果: " << result << std::endl;return result;
});
- 使用有意义的中间变量:为复杂管道的各阶段使用命名变量
// 不好的做法:难以调试的单一长表达式
auto result = data | std::views::filter(...)| std::views::transform(...)| std::views::take(...);// 更好的做法:使用中间变量
auto filtered = data | std::views::filter(...);
auto transformed = filtered | std::views::transform(...);
auto result = transformed | std::views::take(...);
Ranges最佳实践
设计模式
使用Ranges库时的几种有效设计模式:
- 管道模式:使用管道操作符链接数据处理步骤
auto result = data| std::views::filter(pred) // 第一步:过滤| std::views::transform(func) // 第二步:转换| std::views::take(n); // 第三步:限制数量
- 工厂函数模式:创建视图工厂函数封装常用操作
// 封装常用的数据处理操作
auto only_positives(auto&& range) {return range | std::views::filter([](auto x) { return x > 0; });
}auto doubled(auto&& range) {return range | std::views::transform([](auto x) { return x * 2; });
}// 组合使用
auto result = data | only_positives | doubled | std::views::take(5);
- 适配器模式:将现有函数转换为适用于Ranges的形式
// 传统函数
bool is_prime(int n) {if (n <= 1) return false;// ... 素数检查逻辑
}// 适配为视图过滤器
auto prime_filter = [](auto&& range) {return range | std::views::filter([](int n) { return is_prime(n); });
};// 使用
auto primes = numbers | prime_filter | std::views::take(10);
代码风格指南
Ranges代码的推荐风格:
- 单行展示简单操作,多行展示复杂操作
// 简单操作可以单行
auto even = numbers | std::views::filter([](int n) { return n % 2 == 0; });// 复杂管道应该分行,提高可读性
auto processed = data| std::views::filter([](const auto& item) { // 复杂过滤条件return item.status == "active" && item.value > threshold;})| std::views::transform([](const auto& item) {// 复杂转换逻辑return process_item(item);})| std::views::take(10);
- 提取复杂谓词和变换函数
// 不好的做法
auto view = data | std::views::filter([](const auto& x) { /* 长而复杂的条件 */ }
);// 好的做法
auto is_valid = [](const auto& x) { // 长而复杂的条件,现在有了名称return /* ... */;
};
auto view = data | std::views::filter(is_valid);
- 使用有意义的中间变量
// 直接处理
auto result = getRawData() | std::views::filter(is_valid)| std::views::transform(normalize)| std::views::transform(to_output_format);// 更清晰的分步处理
auto raw_data = getRawData();
auto valid_data = raw_data | std::views::filter(is_valid);
auto normalized = valid_data | std::views::transform(normalize);
auto formatted = normalized | std::views::transform(to_output_format);
性能注意事项
优化Ranges代码性能的关键点:
- 适当时机具体化结果:对多次使用的中间结果进行具体化
auto filtered = data | std::views::filter(pred);// 如果filtered会被多次使用,考虑具体化
std::vector<int> filtered_vec(filtered.begin(), filtered.end());// 现在可以高效地多次使用
process1(filtered_vec);
process2(filtered_vec);
- 避免重复昂贵的计算:对高成本操作的结果进行缓存
// 如果transform中的操作很昂贵,可以考虑缓存结果
auto expensive_calc = data | std::views::transform([](int n) {return very_expensive_calculation(n);
});// 具体化以避免重复计算
auto cached = std::vector<result_type>(expensive_calc.begin(), expensive_calc.end());
- 合理组合操作顺序:某些操作顺序比其他更高效
// 通常,先过滤再转换更高效
auto efficient = data| std::views::filter(pred) // 先减少元素数量| std::views::transform(func); // 然后转换减少后的元素// 而不是
auto less_efficient = data| std::views::transform(func) // 先转换所有元素| std::views::filter(pred); // 然后丢弃一些转换结果
总结
C++20的范围(Ranges)库为C++带来了函数式编程的优雅和表达力。通过本文介绍的高级特性和技巧,你已经了解了如何:
- 创建复杂的视图组合来处理嵌套数据结构
- 设计和实现自定义视图以扩展Ranges库功能
- 优化Ranges代码的性能,避免常见陷阱
- 将Ranges与其他C++20特性(如概念和协程)结合使用
- 应用Ranges库解决实际问题,如文本处理和数据分析
Ranges库不仅使代码更加简洁和声明式,还提供了强大的组合能力,使我们能够以模块化方式构建复杂的数据处理管道。随着实践经验的积累,你会发现Ranges库能极大地提高C++编程的生产力和代码质量。
在下一篇文章中,我们将探讨C++20的另一个重要特性:模块(Modules),它如何简化代码组织,加速编译,并解决头文件包含的各种问题。
这是我C++学习之旅系列的第五十二篇技术文章。查看完整系列目录了解更多内容。