我正在使用范围库来帮助在我的类中过滤数据,像这样:
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()
函数中构造了一个新的vector。为了保存这个开销,我想知道是否可以/建议直接从函数返回range?
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;
};
型
如果可能的话,我是否需要考虑一些终身的因素?
我还想知道是否可以/建议将范围作为参数传入,或者将其存储为成员变量。或者范围库更适合在单个函数的作用域内使用?
7条答案
按热度按时间dfddblmv1#
在c++23中,可以使用std::generator和
co_yield
std::ranges::elements_of字符串
工作演示(GCC 13.1不带
std::ranges::elements_of
):https://godbolt.org/z/oehd59oEztv6aics12#
这是在OP的评论部分被问到的,但我想我会在回答部分回答它:
Ranges图书馆似乎很有希望,但我对这辆返回的汽车有点担心。
请记住,即使添加了
auto
,C++也是一种强类型语言。在您的情况下,由于您返回evens
,因此返回类型将与evens
相同。(技术上它将是evens
的值类型,但evens
无论如何都是值类型)实际上,您可能真的不想手动输入返回类型:
std::ranges::filter_view<std::ranges::ref_view<const std::vector<int>>, MyClass::getEvens() const::<decltype([](int i) {return ! (i % 2);})>>
(141个字符)evens
是在函数内部定义的lambda,两个不同的lambda类型即使基本相同也会不同,所以这里实际上没有办法获得完整的返回类型。虽然在不同的情况下是否使用
auto
可能会有争论,但我相信大多数人都会在这里使用auto
。再加上你的evens
也是用auto
声明的,输入类型只会让这里的可读性降低。那么,如果我想访问一个子集(例如偶数),我有什么选择呢?有没有其他方法我应该考虑,有或没有范围库?
根据您如何访问返回的数据以及数据的类型,您可能会考虑返回
std::vector<T*>
。views
实际上应该从头到尾查看。虽然您可以使用views::drop
和views::take
限制为单个元素,但它没有提供下标操作符(尚未)。也会有计算上的差异。
vector
需要预先计算,其中views
是在迭代时计算的。所以当你这样做时:字符串
在引擎盖下,它基本上是这样做的:
型
根据数据量和计算的复杂性,
views
可能会快得多,或者与vector
方法差不多。另外,您可以轻松地在同一范围内应用多个过滤器,而无需多次迭代数据。最后,您可以始终将
view
存储在vector
中:型
所以我的建议是,如果你有范围库,那么你应该**使用它。
如果不是,则
vector<T>
,vector<T*>
,vector<index>
取决于T的大小和可复制性。bqucvtff3#
正如你所看到的here,一个范围就是你可以调用
begin
和end
的东西,仅此而已。例如,您可以使用
begin(range)
的结果(它是一个迭代器)来遍历range
,使用++
运算符来推进它。一般来说,回顾我上面链接的概念,只要conext代码只需要能够调用
begin
和end
,就可以使用范围。显然,如果你的意图是将
evens
传递给一个需要std::vector
的函数,(例如,它是一个你不能改变的函数,它在我们正在谈论的实体上调用.push_back
),你显然必须从filter
的输出中生成std::vector
,我会通过字符串
但是如果你传递给
evens
的所有函数都是在它上面循环,那么型
就可以了
关于生命周期的考虑,视图指向一个值的范围,就像指针指向被指向的实体一样:如果后者被销毁,前者将被悬挂,并且不正确地使用它将是未定义的行为。这是一个错误的程序:
型
ia2d9nvy4#
标准中对STL的组件的使用没有限制。当然,有最佳实践(例如,
string_view
而不是string const &
)。在这种情况下,我可以预见直接处理视图返回类型没有问题。也就是说,最佳实践还没有决定,因为标准是如此之新,还没有编译器有一个完整的实现。
我认为,你可以选择以下几点:
字符串
biswetbf5#
您可以将
ranges::any_view
用作任何范围或范围组合的类型擦除机制。字符串
我在STL范围库中看不到任何等效的;如果可以的话,请编辑答案。
编辑:ranges::any_view的问题是它非常慢和低效。参见https://github.com/ericniebler/range-v3/issues/714。
tyg4sfes6#
我不会对RangesV 3发表评论,所以ranges::any_view不是一个选项。只在标准库范围C++20版本上发言。
所以我遇到了一个类似的问题,当我试图使用视图来返回一个所选元素的非所有视图,并能够使用它与一个常规向量相同的接口,例如,不必双重解引用(指向值的指针或迭代器的向量。)下面是尝试在tokenizer方法中使用视图的结果,该方法可以消除空格/换行符标记。REDUNDANT_TOKEN_KINDS是将被过滤的令牌类型的枚举的初始化器列表。例如TokenType::whitespace注意:rvector等效于下面代码中的std::vector。
字符串
现在,这很好,但是返回类型是什么呢?如果我想创建另一个方法,比如
parse_tokens(token_view_type token_view)
,我不能这样做,因为我不能确定token视图的返回类型。我可以这样做(使用自动参数类型):
型
但这是非常丑陋和不安全的代码,这意味着我的代码库的其余部分将不得不依赖于一个单一的方法sanitize(),它的未知返回类型。我将不得不传递auto,直到我将范围的元素复制到向量中。我可以指定范围的概念::参数中的范围。这更好。但这仍然给我们留下了sanitize()返回类型的问题这对程序员来说并不明显。此外,这将创建parse_tokens方法的无关模板示例。
型
所以一般来说,我会说不建议返回一个范围或视图,因为这意味着所有后续代码都必须适应这种模式。就像你说的,在封装的环境(方法或方法集合)中进行所有算法处理,然后将所需结果作为迭代器或引用的向量返回会更聪明。
我的解决方案是返回一个包含“selected”或“filtered”值的std::reference_wrappers的vector。代码如下:
型
这有一个小缺点,即在迭代时不能直接返回,你必须调用它->get()来获取底层引用对象。
型
总的来说,函数是相同的,并且使代码更加清晰(不是很漂亮吗?)所有的指针逻辑都在std::reference_wrapper的引擎盖下处理,所以你不会弄乱指针。
7z5jn7bk7#
最好在头文件中声明一个返回范围的函数,并在cpp文件中定义它
1.用于编译防火墙(编译速度)
1.阻止语言服务器发疯
1.为了更好地分解代码,
然而,有一些并发症使其不可取:How to get type of a view?
auto
ranges::any_view