【问题标题】:How do ranges::views achieve O(1) complexity?range::views 如何实现 O(1) 复杂度?
【发布时间】:2021-07-06 20:32:27
【问题描述】:

在最近介绍的 C++20 Ranges 中,我知道 views 通过使用视图适配器实现可组合性。我也知道视图不拥有它们的元素,它们的本质是惰性的,也就是说它们只在需要时才进行实际计算。

视图如何在移动、复制和分配操作中实现O(1) 的复杂性?我的可能答案是,视图只是“待计算”操作的描述,它们只是指数据及其转换。

不过,这听起来像是视图只是承担了表达我们编码序列的工作,并且只有在传递给一些急切的事物(例如算法)时,它们才会在这个特定的单个调用中体现所有计算负载。

后续问题:我可以理解如何实现O(1) 复制,本质上指的是可复制对象(尽管我不知道ranges::views 是否这样做)。但我无法理解这将如何在分配操作中发挥作用。同样,一个可能的答案是,因为所有这些都发生在编译时,那么再次只是“描述”分配是O(1) 操作。但是改变视图查看的std::vector<int> 是一个运行时操作(很棒的example)。这仍然是O(1) 操作吗?

【问题讨论】:

    标签: c++ complexity-theory c++20 range-v3 std-ranges


    【解决方案1】:

    您认识到视图不拥有它们引用或操作的元素。但是您似乎不明白这就是为什么这些操作是 O(1)。

    如果你有这个:

    vector<int> v = {...};
    auto *vptr = &v;
    
    auto *vptr2 = vptr;
    vptr = vptr2;
    

    vref2相对于v.size()的初始化复杂度是多少?相对于v.size(),分配给vptr 的复杂度是多少?

    它们都是 O(1),因为它们只是复制指针

    vptr 指向v;它不拥有它。作为指针如何它不拥有v。这也是复制指针是一个 O(1) 操作的原因:因为指针的大小不关心它指向的数据的大小。

    视图也是如此。视图类型存储底层范围的迭代器/哨兵。指针是一种迭代器,但迭代器的工作方式与指针很相似,因为它们指向一个值序列,而不是成为序列本身。范围由起始迭代器和代表该范围结束的值定义(有时是另一个迭代器,有时是可以针对迭代器进行测试的通用对象)。

    迭代器类型不知道也不关心序列中有多少元素。迭代器概念对序列中的位置进行建模;从概念上讲,它不知道终点是什么。

    因此,相对于它们指向的范围的大小,复制一个迭代器(通常)是 O(1)。移动和复制/移动分配也是如此。

    【讨论】:

    • 正如我在后续问题中所描述的,这正是我所期望的答案。谢谢你!但是,当视图实际上描述了要对其引用的数据进行的计算时会发生什么?例如views::transform。那么指针是如何完成这项工作的呢?
    • @gonidelis:它存储要调用的函数。不管那个函数是什么,它的复制/移动操作与范围的大小无关。
    • “迭代器无法知道范围有多大” - 他们可以。在某些情况下,他们必须这样做。例如,filter_view::iterator 必须知道正确的 ++ 的终点在哪里。
    • @Barry 你说的是counter_iterators?
    猜你喜欢
    • 2016-04-21
    • 1970-01-01
    • 1970-01-01
    • 2013-03-21
    • 2012-07-12
    • 2019-07-27
    • 2022-07-07
    • 2017-09-01
    • 1970-01-01
    相关资源
    最近更新 更多