了解带有临时参数和纯右值参数的 std::ranges::sort 的返回类型

ggu*_*lia 4 c++ sorting c++20 prvalue std-ranges

我在理解类型何时推导出std::ranges::dangling何时使用命名局部变量与纯右值作为std::ranges::sort算法的参数时遇到问题。例如,我有一个函数将 a 返回prvalue 到标准容器,比如 std::vector ,我直接将其用作 的参数std::ranges::sort,然后我期望std::ranges::dangling在尝试取消引用迭代器时收到编译错误,这就是我得到的:

#include <vector>
#include <algorithm>

auto get_data(){
    return std::vector<int>{1, 2, 99, 5, 9, 4};
}

auto get_sorted(){
    return std::ranges::sort(get_data());
}


int main(){
    auto it = get_sorted();
    *(it-1); //Compiler Error because it is a std::ranges::dangling iterator
}
Run Code Online (Sandbox Code Playgroud)

但是,如果我稍微更改get_sorted上面的函数以首先捕获命名变量中的向量并使用它来返回 的结果std::ranges::sort,那么我不会得到悬空迭代器,即使main命名变量中分配的向量应该是函数get_sorted返回后销毁:

auto get_sorted(){
    auto vec = get_data();
    return std::ranges::sort(vec);
}


int main(){
    auto it = get_sorted();
    *(it-1); //Okay result  = 99
}
Run Code Online (Sandbox Code Playgroud)

即使我更改get_sorted为使用本地声明的基于命名变量的容器,我也会得到一种行为,编译器不会抱怨在其调用者(例如函数)中取消引用悬空迭代器main

//doesn't return a std::ranges::dangling
auto get_sorted(){
    std::vector<int> vec{1, 2, 99, 5, 9, 4};
    return std::ranges::sort(vec);
}
Run Code Online (Sandbox Code Playgroud)

当我将纯右值向量传递给std::ranges::sort算法时,我再次得到std::ranges::dangling预期的

//returns a std::ranges::dangling
auto get_sorted(){
    std::vector<int> vec{1, 2, 99, 5, 9, 4};
    return std::ranges::sort(std::vector<int>{1, 2, 99, 5, 9, 4});
}
Run Code Online (Sandbox Code Playgroud)

对于编译器没有收到错误的情况,当我std::ragnes::dangling使用选项进行编译时,我确实观察到运行时错误,fSanitize=address这可能是因为在函数内的命名变量中分配的向量get_sorted一旦后一个函数就超出了范围回。

不过,我想了解为什么在函数中使用命名变量会更改get_sorted函数的返回类型,并可能提供有关如何正确使用临时和纯右值容器以std::ranges::dangling尽可能获取的指南。

Raf*_*Raf 5

实际上,我认为我很确定这是因为代码正在创建超出范围的临时对象。https://en.cppreference.com/w/cpp/ranges/dangling上的示例代码完美地显示了您正在做的事情,产生了这种行为。

代码 ( return std::ranges::sort(get_data());) 触发与示例中相同的静态断言:static_assert(std::is_same_v<std::ranges::dangling, decltype(dangling_iter)>);

当源超出范围时,这将产生悬空类型/在排序操作后将变得不可访问。

你做了什么:

//doesn't return a std::ranges::dangling
auto get_sorted(){
    std::vector<int> vec{1, 2, 99, 5, 9, 4};
    return std::ranges::sort(vec);
}
Run Code Online (Sandbox Code Playgroud)

欺骗这个安全网进行不安全的操作 - 使用返回的迭代器的代码试图访问释放的内存(这是一个很大的禁忌)。在标准库中没有好的方法可以检测到这一点,因此 std::ranges 库不会保护您免受它的侵害。对于悬空返回类型,可以使用 templates 检测临时参数。因此std::ranges::sort,甚至可能不进行任何排序,如果有效实现,它只是短路返回悬空类型。

局部变量在其当前作用域之后将变得不可用,这意味着任何引用都将变得悬空。std::ranges 库正在尽最大努力保护您免受简单情况(例如您的第一个情况)的影响。

为了使代码正常工作,您必须在任何迭代器操作期间保持向量可用:

auto get_data(){
    return std::vector<int>{1, 2, 99, 5, 9, 4};
}

auto get_sorted(std::vector<int>& data){
    return std::ranges::sort(data);
}


int main(){
    auto data = get_data(); // keep the vector in the same scope as the iterator operations
    auto it = get_sorted(data);
    *(it-1);
}
Run Code Online (Sandbox Code Playgroud)

只要使向量在父作用域中保持活动状态,您还可以在子作用域中执行迭代器操作。

现场演示: https://coliru.stacked-crooked.com/a/b8b2945cfda85a20

正如您所看到的,向量在 get_sorted 完成之前就被销毁了。

请记住,容器包含数据,而算法仅提供该数据的视图(带有迭代器),或者它们修改现有数据,它们不进行复制(除非像 std::copy 那样明确指定)。

  • 命名变量不会返回悬空,因为直接调用“sort”后,没有任何悬空。排序调用后数据仍然可以访问。‘return’后就无法访问了。`sort(std::vector&lt;int&gt;{})` 临时参数在 `sort` 调用完成后立即被销毁,这意味着它将返回悬空数据。编译器/范围库不能保护您返回一个将变得危险的迭代器(该库无法在所有情况下支持它)。 (2认同)