c++ - 带有 std::span 的 C++ Ranges-v3:从函数返回范围视图时的中间对象的所有权
问题描述
我是 Eric Niebler 的 range-V3 库(到目前为止我很喜欢!)的初学者,但是在从函数返回 range 时遇到了一些问题。我想我找到了问题所在,但在这种情况下对范围 API 的默认行为感到有些惊讶。由于我没有在其他地方找到任何关于此问题的参考,并且这花费了我相当多的时间,所以我将我的问题写得比较宽泛,希望这对将来的其他人有所帮助。
以下最小示例中存在问题,这会导致未定义的行为。
#include <iostream>
#include "range/v3/all.hpp"
#include "nonstd_span.h"
auto from_span() {
// make this static for the array to persist after the fct returns
static int my_array[10] = { 1,2,3,4,5,6,7,8,9,10 };
auto my_span = nonstd::span<int>(my_array, 10);
return ranges::views::all(my_span);
}
int main() {
std::cout << from_span() << std::endl;
return 0;
}
我想要实现的目标: 我的程序中有一些持久(且恒定)的连续数据,我试图通过范围对其进行操作。range::views 的可组合性、惰性求值以及非拥有性质使 range 看起来像是完成任务的完美工具。我想使用范围启用的简洁语法,以及在函数之间将这些作为非常轻量级的对象传递。
在大多数演示范围的代码示例中,范围操作的对象是在与范围本身相同的范围内创建的,因此一旦范围完成评估并且一切正常,它们就会一起被破坏。
在我的情况下,范围操作的实际数据是外部拥有的,我可以保证它在范围视图的生命周期内持续存在。对于上面的示例,我只是my_array
将内存范围设为静态,即内存范围归函数所有,并且一旦返回,数据就会持续存在(这可能是有问题的风格,但我相信演示并没有错)。
要从这个原始 int 数组创建一个范围,似乎span是一种选择工具,可以轻松地将这个裸露的、连续的数据包装为迭代器以与范围视图交互:它是非拥有的且重量轻。由于我使用的一些编译器还不支持 C++20,所以我使用 Martin Moene 的span-lite而不是std::span
,但还使用 Tristan Brindle 的span库测试并重现了该行为。
问题:
我对此不确定,但我认为上面示例的问题是 ranges::views::all(my_span)
范围视图对象不拥有 span 对象的所有权。main
尽管在函数中调用范围时底层数据(int 数组)仍然存在,但my_span
对象在函数退出时被破坏(我可以看到在评估视图之前调用了 span 析构函数)。在平台和各种编译器上,我用(g++ 7.4.0、Clang 6.0.0、MSVC 16.5.5)测试了这个代码通常似乎可以工作,但这只是因为以前的my_span
对象的位仍然存在并且没有当范围视图评估在main
.
我所期望的行为/API
因为span
应该是非常轻量级的并且ranges::views
被设计为非拥有数据的视图,所以我期望由创建的视图ranges::views::all(my_span)
复制span
对象并获得它的副本的所有权。这将允许用户在组合视图时不必考虑所有中间对象的生命周期,并在函数和范围之间传递它们,只要底层数据仍然存在(也许我作为一个天真的新手对范围的期望在这里有缺陷?) . 此外,当从其他视图组合新视图时,是否需要担心保持较低级别的视图处于活动状态,以防它们超出范围而新组合的视图没有?
我尝试强制转换为 r 值引用以触发移动构造函数并强制视图获取所有权ranges::views::all(std::move(my_span))
,但这似乎没有实现或工作。
我尝试过的其他一些解决方案:
- 拥有
my_span
外部范围并通过from_span
引用将其传递给它。这行得通。 my_span
与函数的范围一起返回,例如通过 astd::unique_ptr
澄清所有权并防止返回时复制auto from_span() { using namespace ranges; static int my_array[10] = { 1,2,3,4,5,6,7,8,9,10 }; auto span_ptr = std::make_unique<nonstd::span<int>>(my_array, 10); return std::make_tuple(views::all(*span_ptr), std::move(span_ptr)); } int main() { auto [rng, my_span_ptr] = from_span(); std::cout << rng << std::endl; return 0; }
还可以为跨度构建一个小型内存/生命周期管理系统,这些跨度由外部拥有。
这些解决方案对我来说似乎都不是特别优雅,它们会为在这种情况下使用范围视图添加大量样板代码和复杂性(缩短语法并且不必考虑生命周期正是我想要做的事情达到)。
我觉得我可能在这里遗漏了一些东西,并且应该有一个更优雅的解决方案,其中范围视图获取所有权/复制它所组成的轻量级对象(例如跨度或其他视图)。
是span
不是任务的正确工具?似乎它是为诸如此类的用例而创建的?
解决方案
有范围库可能不知道那nonstd::span
是一个view
. 你需要通过专业来告诉它ranges::enable_view
。没有它,范围库认为它就像一个向量,当你将它的左值传递给views::all
你时,你会得到一个引用本地span
对象而不是span
.
在最近的过去,range-v3 会使用启发式方法来猜测(正确地)这span
是一个视图,并且您的代码会正常工作。它根据 C++ 委员会的要求进行了更改,该委员会不喜欢这种启发式方法。公平地说,它有时会猜错。
推荐阅读
- html - 从 new FormData() 创建新数组,然后使用 Angular HttpClient 发送到服务器
- javascript - reveal.js 与 plotly.js 和 chart.js 导出 pdf
- html - 如何确定移动浏览器中的实际视口/窗口高度?
- game-development - 无法使用 get_node() 定位子标签(GD 脚本)
- r - 指定导入包的版本 (GitHub) R 描述文件
- amazon-web-services - 使用 Dockerfile 构建容器映像以服务 Flask Web 应用程序时出错
- python - 如何在python中使用条件增加/更改数据集中的值
- python - 无论如何要使这个模型适合这些数据点?
- python - 在 PsychoPy 中沿弯曲路径移动刺激
- c - 永远卡在输入请求上 (C)