现代 C++20 实战高手课
卢誉声
Autodesk 首席工程师
3781 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 29 讲
现代 C++20 实战高手课
15
15
1.0x
00:00/00:00
登录|注册

12|Ranges(二):用“视图”破除函数式编程之困

你好,我是卢誉声。
上一讲,我们重点讨论了 C++ 传统函数式编程的困境,介绍了 Ranges 的概念,了解到 range 可以视为对传统容器的一种泛化,都具备迭代器等接口。但与传统容器不同的是,range 对象不一定直接拥有数据。
在这种情况下,range 对象就是一个视图(view)。这一讲,我们来讨论一下视图,它是 Ranges 中提出的又一个核心概念,是 Ranges 真正解放函数式编程的重要驱动力(项目的完整代码,你可以这里获取)。

视图

视图也叫范围视图(range views),它本质是一种轻量级对象,用于间接表示一个可迭代的序列。Ranges 也为视图实现了视图的迭代器,我们可以通过迭代器来访问视图。
对于传统 STL 中大部分可接受迭代器参数的算法函数,在 C++20 中都针对视图和视图迭代器提供了重载版本,比如 ranges::for_each 等函数,这些算法函数在 C++20 中叫做 Constraint Algorithm
那么 Ranges 库提供的视图有哪些呢?
我把视图类型和举例梳理了一张表格,供你参考。
所有的视图类型与函数,都定义在 std::ranges::views 命名空间中,标准库也为我们提供了 std::views 作为这个命名空间的一个别名,所以实际开发时我们可以直接使用 std::views。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

C++20中引入的Ranges库为函数式编程带来了新的解放力量。通过引入视图(view)的概念,Ranges库实现了对可迭代序列的轻量级间接表示。视图本质上是一种轻量级对象,用于表示可迭代序列,而不直接拥有数据。开发者可以使用视图工厂和适配器来构建视图对象,实现对可迭代序列的便捷操作。文章介绍了视图的基础概念和接口设计,以及如何自定义实现视图类。此外,文章还详细介绍了C++20中提供的视图类型和函数,以及视图工厂的使用方法。通过示例代码,读者可以感性地认识如何使用视图工厂来创建视图对象,以及如何使用视图适配器进行数据处理。除了介绍视图的基本概念,文章还展示了函数式编程案例,比较了传统STL算法和使用视图的代码实现,凸显了视图在代码可读性和功能性上的优势。另外,文章还介绍了视图管道的概念,通过视图管道和视图适配器,读者可以组织出C++中非常优雅的函数式代码。文章内容深入浅出,适合读者快速了解Ranges库的特点和应用。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《现代 C++20 实战高手课》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(6)

  • 最新
  • 精选
  • tang_ming_wu
    对比函数式编程实现和传统编程实现,我个人觉得函数式编程只是伪需求和一小部分人的自嗨:(1)不方便调试(2)不方便设计解耦(3)不方便维护(4)不方便阅读理解。

    作者回复: 首先,函数式编程本身并不是伪需求,如果你了解并习惯了函数式编程的一些基本原则和解决问题的思路,你会发现在处理特定问题时采用函数式编程的思路是很顺畅的。 其次,现在讨论函数式编程时很多人可能的确在“自嗨”,因为在我看来函数式编程并不比传统的过程化或者面向对象等范式更”高级”,我认为这些范式没有绝对优劣。以我个人为例,在处理复杂数据尤其是并行编程时,会优先选用函数式编程的思路组织计算过程,在实现函数细节时,我会更习惯使用过程化/结构化的思路,在组织整个系统的架构或对系统进行建模时,我可能更倾向于基于面向对象的设计方法并进行模块划分——在合理的场景使用每个人所认定的更合理的解决方案就行。计算机世界没有“银弹”,不同的技术或者方法论对我们来说都只是解决实际问题的一个“工具”,我们不需要教条化,该用什么就用什么,仅此而已。 但是,我们依然需要去学习,去尝试使用一些不同的思路。由于我们大部分人入门时都以结构化/过程与面向对象为主要范式的语言开始学习,自然会习惯采用这些熟悉的思路去解决问题,一旦换了一种思维方式(比如函数式编程),一开始就会无所适从并且抵触。反之,如果我们一开始学习的不是C/C++/Java这类语言,而是LISP或者Erlang这类函数式语言,那么你可能会觉得用函数式的思路去解决问题是一个非常自然的事情,就和你现在习惯使用C++编写代码一样。 同时,我也不觉得函数式编程有什么地方会让我们不方便调试,反而由于习惯了变量的不可变性(无副作用),很多时候反倒容易在调试更快确定发生问题的位置。 至于设计解耦也和函数式编程完全没有关系。也许因为我的例子中只演示了如何使用函数式编程处理数据,所以你觉得函数式编程的耦合性很高。但耦合性更多体现在系统的模块划分和组织,而不是我们针对某一个数据的处理过程中。我们使用函数式编程来组织系统时,可以根据函数和业务的关系分析函数之间的内聚性,然后进行模块划分,并不会将函数都放在一个模块中,依然可以实现高内聚、低耦合的系统设计。我的示例中只是在实现一个算法,这些代码必然是要以一种高内聚的方式来组织的,而且你会发现数据处理代码的各个部分由于只关注如何解决一个问题,因此数据流的每一个处理函数反倒有更强的内聚性。 代码的可维护性就更取决于我们自己的系统设计和编码习惯,和函数式编程没有任何必然关系。这里也需要再提一下,函数式编程中的变量不可变性有时候对于代码维护反倒是有益处的。 说实话,我一开始也不习惯(自然也不认同)函数式编程的思路和理念, 但在实际工作中慢慢去尝试换一种思路解决问题后,突然发现自己已经非常习惯在数据处理过程中使用map/filter/reduce之类的高阶函数来组织代码,现在非常享受这种分离关注点、无副作用和函数幂等性带给我的“快乐”,也许这也不是纯粹的函数式编程,但在汲取其中一些核心理念后,我在处理复杂数据时的编码风格的确产生了很大改变,并且让我感到受益良多。

    2023-03-14归属地:广东
    5
  • Family mission
    views::reverse和ranges::reverse_view的使用 #include <iostream> #include <ranges> int main() { namespace ranges=std::ranges; namespace views = std::views; static constexpr auto il = {3, 1, 4, 1, 5, 9}; ranges::reverse_view rv{il}; for (int i : rv) std::cout << i << ' '; std::cout << '\n'; for (int i : il | views::reverse) std::cout << i << ' '; std::cout << '\n'; // operator[] is inherited from std::view_interface for (auto i{0U}; i != rv.size(); ++i) std::cout << rv[i] << ' '; std::cout << '\n'; }

    作者回复: 对views::reverse和ranges::reverse_view的使用没问题,赞

    2023-12-13归属地:上海
  • Family mission
    作者你好,感觉ranges作用以及功能性都不错,请教个问题 template <class Element, size_t Size> class ArrayView : public std::ranges::view_interface<ArrayView<Element, Size>> { 中class ArrayView 继承std::ranges::view_interface<ArrayView<Element, Size>>不太理解的点是继承模板类不都是类名<模板类型>这种么,这个写法是两个尖括号是啥意思

    作者回复: 这个是嵌套模板 首先是std::ranges::view_interface<T> 然后T是ArrayView<Element, Size> 所以最后就是std::ranges::view_interface<ArrayView<Element, Size>>

    2023-12-13归属地:上海
  • 常振华
    函数式编程的可读性真是差,非常差,C++发展越来越倒退了

    作者回复: 函数式编程在数据处理领域极为有用,这也是为什么像Scala这样的编程语言能占有一席之地的原因之一。在数据不可变性基础上,对数据做变换、转化和处理,使用函数式编程在多线程场景下必不可少。相较于加锁,函数式编程能以更低成本实现数据的并发处理,从这个角度看,支持函数式编程是有意义的。

    2023-10-18归属地:广东
  • 大熊猫有宝贝
    工厂和工具函数之间的关系该怎么理解呢?

    作者回复: 视图工厂包含: (1)视图类:视图对象的类型 (2)工具函数:用于从特定的视图类型,创建视图对象

    2023-04-03归属地:上海
  • 📷全程不笑🏀
    老师好,请教个问题,我的环境是ubuntu20.04, gcc版本11.1.0。 工厂小节中的示例代码编译报错了, istream_view与istream相关。 第25行的视图初始化应该为小括号吧,大括号{}我这边编译报错。另外28行的views::istream也编译报错,替换成ranges::istream_view运行正常,不知道是不是我环境的问题?

    作者回复: 目前这个时间点,gcc 对新标准支持并不好。从支持程度上来说,Visual C++ > clang > gcc。 你可以尝试 Visual Studio 2022 来进行编译。另外建议gcc升级到12.2。

    2023-02-10归属地:广东
收起评论
显示
设置
留言
6
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部