【问题标题】:Is it possible / advisable to return a range?是否可以/建议返回一个范围?
【发布时间】:2021-08-15 09:33:06
【问题描述】:

我正在使用范围库来帮助我的课程中的文件管理器数据,如下所示:

class MyClass
{
public:
    MyClass(std::vector<int> v) : vec(v) {}

    std::vector<int> getEvens() const
    {
        auto evens = vec | ranges::views::filter([](int i) { return ! (i % 2); });
        return std::vector<int>(evens.begin(), evens.end());
    }

private:
    std::vector<int> vec;
};

在这种情况下,getEvents() 函数中构造了一个新向量。为了节省这个开销,我想知道是否可以/建议直接从函数返回范围?

class MyClass
{
public:
    using RangeReturnType = ???;

    MyClass(std::vector<int> v) : vec(v) {}

    RangeReturnType getEvens() const
    {
        auto evens = vec | ranges::views::filter([](int i) { return ! (i % 2); });
        // ...
        return evens;
    }

private:
    std::vector<int> vec;
};

如果可能,是否有任何我需要考虑的生命周期因素?

我也很想知道是否可以/建议将范围作为参数传递,或者将其存储为成员变量。还是范围库更适合在单个函数的范围内使用?

【问题讨论】:

  • 为什么不返回auto
  • 也许你想要的只是一个getView函数,它返回vec作为视图,even是一个免费的lambda,可以被views::filter调用
  • 您希望将其作为vector 返回的唯一原因是,如果您知道每次调用它时都希望将其存储为新的vector。否则,您可以随时返回一个视图并在需要时为其创建一个向量。
  • 我如何创建一个视图来返回它?
  • 我想出了一个... auto range = container |范围::views::all;

标签: c++ c++20 range-v3


【解决方案1】:

标准中对 STL 组件的使用没有限制。当然,也有最佳实践(例如,string_view 而不是 string const &amp;)。

在这种情况下,我可以预见直接处理视图返回类型没有问题。也就是说,最佳实践尚未确定,因为标准太新了,还没有编译器有完整的实现。

在我看来,您可以选择以下内容:

class MyClass
{
public:
    MyClass(std::vector<int> v) : vec(std::move(v)) {}

    auto getEvens() const
    {
        return vec | ranges::views::filter([](int i) { return ! (i % 2); });
    }

private:
    std::vector<int> vec;
};

【讨论】:

    【解决方案2】:

    这是在op的评论部分提出的,但我想我会在答案部分做出回应:

    Ranges 库看起来很有希望,但我对这辆返回的汽车有点担心。

    请记住,即使添加了auto,C++ 也是一种强类型语言。在您的情况下,由于您要返回evens,因此返回类型将与evens 的类型相同。 (从技术上讲,它将是 evens 的值类型,但 evens 无论如何都是值类型)

    事实上,你可能真的不想手动输入返回类型:std::ranges::filter_view&lt;std::ranges::ref_view&lt;const std::vector&lt;int&gt;&gt;, MyClass::getEvens() const::&lt;decltype([](int i) {return ! (i % 2);})&gt;&gt;(141 个字符)


    • 正如@Caleth 在评论中提到的那样,事实上,这也行不通,因为evens 是在函数内部定义的 lambda,两个不同的 lambda 的类型即使它们基本相同,也会有所不同,因此实际上无法在此处获取完整的返回类型。

    虽然在不同情况下是否使用auto 可能存在争议,但我相信大多数人会在这里使用auto。另外,您的 evens 也被声明为 auto,输入类型只会降低此处的可读性。


    如果我想访问一个子集(例如偶数),我有哪些选择?我应该考虑使用或不使用 Ranges 库的其他方法吗?

    取决于您将如何访问返回的数据和数据的类型,您可以考虑返回std::vector&lt;T*&gt;

    views 真的应该从头到尾查看。虽然您可以使用 views::dropviews::take 来限制单个元素,但它还没有提供下标运算符。

    也会有计算上的差异。 vector 需要预先计算,其中 views 是在迭代时计算的。所以当你这样做时:

    for(auto i : myObject.getEven())
    {
        std::cout << i;
    }
    

    在后台,它基本上是在做:

    for(auto i : myObject.vec)
    {
        if(!(i % 2)) std::cout << i;
    }
    

    取决于数据量和计算的复杂性,views 可能会快很多,或者与vector 方法大致相同。此外,您可以轻松地在同一范围内应用多个过滤器,而无需多次迭代数据。

    最后,您始终可以将view 存储在vector 中:

    std::vector<int> vec2(evens.begin(), evens.end());
    

    所以我的建议是,如果您有 ranges 库,那么您应该使用它。

    如果不是,则 vector&lt;T&gt;vector&lt;T*&gt;vector&lt;index&gt; 取决于 T 的大小和可复制性。

    【讨论】:

    • 您不能输入偶数的类型,因为正文中的 lambda [](int i) {return ! (i % 2);} 与声明中的 lambda [](int i) {return ! (i % 2);} 类型不同
    【解决方案3】:

    如您所见here,范围就是您可以调用beginend 的东西。仅此而已。

    例如,您可以使用begin(range)(它是一个迭代器)的结果来遍历range,使用++ 运算符来推进它。

    总的来说,回顾我上面链接的概念,只要上下文代码只需要能够在其上调用beginend,就可以使用范围。

    这是否可取或足够取决于您需要用它做什么。显然,如果您的意图是将evens 传递给一个需要std::vector 的函数(例如,它是一个您无法更改的函数,并且它在我们正在谈论的实体上调用.push_back),您显然必须做出filter 的输出中的 std::vector,我将通过

    auto evens = vec | ranges::views::filter(whatever) | ranges::to_vector;
    

    但是如果你传递evens 的所有函数都是循环它,那么

    return vec | ranges::views::filter(whatever);
    

    没问题。

    关于生命周期的考虑,视图对于值的范围就像指针对于指向的实体一样:如果后者被破坏,前者将悬空,并且不当使用它将是未定义的行为.这是一个错误的程序:

    #include <iostream>
    #include <range/v3/view/filter.hpp>
    #include <string>
    
    using namespace ranges;
    using namespace ranges::views;
    
    auto f() {
        // a local vector here
        std::vector<std::string> vec{"zero","one","two","three","four","five"};
        // return a view on the local vecotor
        return vec | filter([](auto){ return true; });
    } // vec is gone ---> the view returned is dangling
    
    int main()
    {
        // the following throws std::bad_alloc for me
        for (auto i : f()) {
            std::cout << i << std::endl;
        }
    }
    

    【讨论】:

      【解决方案4】:

      您可以使用ranges::view_any 作为任何范围或范围组合的类型擦除机制。

      ranges::any_view<int> getEvens() const
      {
          return vec | ranges::views::filter([](int i) { return ! (i % 2); });
      }
      

      我在 STL 范围库中看不到任何等效项;如果可以,请编辑答案。

      【讨论】:

        猜你喜欢
        • 2013-02-02
        • 2022-10-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-01-18
        • 2020-05-03
        • 2017-06-07
        • 2012-05-07
        相关资源
        最近更新 更多