有没有办法在不同范围内共享部分shared_ptr?

Is there a way to share part of a shared_ptr in different ranges?

提问人:Sam Moldenha 提问时间:7/31/2023 更新时间:7/31/2023 访问量:135

问:

所以,如果你想分享过去元素中的所有内容,你可以写如下内容:shared_ptrx

int main(){
    std::shared_ptr<float[]> _vals = std::make_unique<float[]>(70);
    for(uint32_t i = 0; i < 10; ++i)
        _vals[i] = i;
    //range from 2 - end of _vals
    // x = 2 in this case
    std::shared_ptr<float[]> _partial(_vals, &_vals[2]);
    
    for(uint32_t i = 0; i < 5; ++i)
        std::cout<<_partial[i]<<" ";
    std::cout<<std::endl;
    return 0;
}

因此,输出将是:

2 3 4 5 6

所以会指向 的末尾。但是,如果我想让它指向例如,我想知道是否可以分配地址来完成此操作怎么办? 因此,例如,如果这有效:_partial_vals.get() + 2_vals(2-5), (70-end)_partial

int main(){
    std::shared_ptr<float[]> _vals = std::make_unique<float[]>(70);
    for(uint32_t i = 0; i < 70; ++i)
        _vals[i] = i;
    //range from 2 - 5, and then range from 70-73
    std::shared_ptr<float[]> _partial(_vals, &_vals[2]);
    float** begin = &(&_vals[70]);
    float** end = &(&_vals[73]);
    float** beg_2 = &(&_partial[2]);
    for(;begin != end; ++(*begin), ++(*beg_2)){
        beg_2 = begin;
    }

    //so technically _partial[0] would point to _vals[2] but _partial[3] would point to _vals[70]
    for(uint32_t i = 0; i < 5; ++i)
        std::cout<<_partial[i]<<" ";
    std::cout<<std::endl;
    return 0;
}

但是,如果我尝试编译它,我会收到错误:

cannot take the address of an rvalue of type 'float *'
        float** begin = &(&_vals[70]);

有没有办法完成我想做的事情?

C++ C++17 共享 PTR 唯一 PTR

评论

1赞 Eljay 7/31/2023
过去获取数组的地址是合法的,但取消引用它是不合法的(因为它已经过了最后一个有效元素)。这是不合法的,因为这已经过了尽头,即使它没有被取消引用。这是提供的示例代码中的疏忽吗?&_vals[73]
0赞 JaMiT 7/31/2023
“但是,如果我想让它指向例如(2-5),例如(70-end)怎么办” -- 由于您为索引分配了70个元素,因此索引70已经是末尾。所以和 .也许你想改成?_vals(2-5), (70-end)(2-5)70-end60-end

答:

0赞 Mark Ransom 7/31/2023 #1

智能指针都是关于所有权的——谁拥有你分配的内存?你不能让一个智能指针拥有整个数组,而另一个智能指针只拥有其中的一部分,这在物理上是不可能的。

评论

0赞 pts 7/31/2023
但是,所有权可以拆分为子区间。但是,这不会在 .std::shared_ptr 中实现。
0赞 pts 7/31/2023
没有根本原因说明为什么多个重叠的 std::shared_ptrs 不能同时拥有一个数组。但是,这没有实现。
0赞 Mark Ransom 7/31/2023
@HTNW你是对的,那么该代码片段似乎使用了仅在 C++20 中引入的功能。但是这种形式的构造函数确实从原始指针移动,因此以后对原始指针的访问将是无效的。shared_ptrshared_ptr
0赞 HTNW 7/31/2023
@MarkRansom OP 中使用的别名构造函数的形式不会移动。
0赞 Mark Ransom 7/31/2023
@HTNW我很抱歉。我使用 cppreference 作为我的指南,目前还不清楚有两个版本的构造函数采用不相关的参数。
5赞 HTNW 7/31/2023 #2

A 是此作业的错误工具。A 拥有某个对象并指向某个(可能不同的)对象。它没有实现足够的逻辑来执行您想要的映射。(它也不应该实现这种逻辑。这不是一项简单的任务。shared_ptrshared_ptr0 -> 0, 1 -> 1, 2 -> 2, 3 -> 70

实现自己的类来实现此行为。这应该能让你开始:

template<typename T>
class subsequence {
public:
    struct interval { std::size_t begin, length; };
private:
    std::shared_ptr<T[]> storage;
    std::vector<interval> pieces;
public:
    subsequence(std::shared_ptr<T[]> storage, std::initializer_list<interval> pieces)
    : storage(std::move(storage)), pieces(pieces) { }

    T &operator[](std::size_t i) {
        for(interval const &piece : pieces) {
            if(i < piece.length) return storage[piece.begin + i];
            else i -= piece.length;
        }
    }
};

int main() {
    std::size_t const size = 80;
    std::shared_ptr<float[]> vals = std::make_unique<float[]>(size);
    for(uint32_t i = 0; i < size; ++i) _vals[i] = i;

    subsequence partial(vals, {{2, 3}, {70, 3}}); // range from 2 - 5, and then range from 70-73
    for(uint32_t i = 0; i < 6; ++i) std::cout << partial[i] << " ";
    std::cout << "\n";
}
2赞 Eljay 7/31/2023 #3

如果散点跨度需要是原始数组的共享指针,则可以在共享指针对中捕获该信息,然后将它们放在这些对的向量中。

我冒昧地将原始数组设置为 99 个元素,因此第二个跨度是有效的。

#include <iostream>
#include <memory>
#include <utility>
#include <vector>

using std::cout;
using std::exchange;
using std::ostream;
using std::pair;
using std::shared_ptr;
using std::vector;

namespace {

using shared_float_span = pair<shared_ptr<float>, shared_ptr<float>>;
using vector_span = vector<shared_float_span>;

auto operator<<(ostream& out, shared_float_span const& s) -> ostream& {
    auto sep = "";
    auto it = s.first.get();
    auto end = s.second.get();

    while (it != end) {
        out << exchange(sep, " ") << *it;
        ++it;
    }

    return out;
}

auto operator<<(ostream& out, vector_span const& v) -> ostream& {
    auto sep = "";

    for (auto&& x : v) {
        out << exchange(sep, " ") << x;
    }

    return out;
}

} // anon

int main(){
    shared_ptr<float[]> vals = std::make_unique<float[]>(99);

    for(uint32_t i = 0; i < 99; ++i) {
        vals[i] = 1000 + i;
    }

    shared_float_span a{ shared_ptr<float>(vals, &vals[2]), shared_ptr<float>(vals, &vals[6]) };
    shared_float_span b{ shared_ptr<float>(vals, &vals[70]), shared_ptr<float>(vals, &vals[74]) };
    vector_span v{a, b};
    cout << v << "\n";
}
0赞 JaMiT 7/31/2023 #4

所以会指向 的末尾。_partial_vals.get() + 2_vals

不。指针指向单个对象,而不是区域。在您的情况下,指向 。只是一个元素。这样可以访问某个子范围的想法是基于您为如何访问元素而采用的约定。_partial_vals.get() + 2_vals

举个例子:您的循环打印了 的第三个到第七个元素。你的文字表明循环应该“到”(第 70 个元素)的结尾,但它没有。 为什么?因为你的循环在索引 5 而不是 68 处结束。这不是固有的,而是你如何使用的结果。_vals_vals_partial_partial

同样,循环从索引 0 (of ) 开始。这是你的选择。虽然从索引 0 开始是常规的,但它只是常规的。在特定情况下,指向数组的第三个元素。这意味着表达式是合法的,并指向数组的第一个元素。(这个表达式可能在实践中也有效,但 cppreference.com 说这种行为是未定义的。你的起点,就像你的终点一样,不是固有的东西,而是你如何使用的结果。_partial_partial_partial.get()[-2]_partial[-2]_partial_partial

结果是,你想要的行为不是关于所有权,而是关于如何使用你拥有的数据。

重新构建问题

因此,我们可以从您的问题中消除共享所有权。你有一个数组,并且你想要一种方法来迭代数组的子范围。数组的所有权并不重要;这个问题只需要一个数组的存在和一种指定子范围的方法。

简单的情况是子范围是连续的。在这种情况下,子范围可以通过其开头和结尾来指定。一种常见的方法是提供指向开头 () 和大小的指针(如果我们通过您的循环,或者如果我们使用“结束”)。你知道该怎么做。您的问题是如何指定一个不连续的子范围。_partial568_vals

使用非连续范围时面临的问题是,内置运算符是为连续范围设计的。对象 和 必须位于相邻的内存位置。这就是这个运算符的工作方式。std::shared_ptr::operator[][]_partial[2]_partial[3]

答案

那么可以做些什么呢?在 C++ 17 中,您可以编写一个适配器类。这样的类可以包装一个数组,并提供一个可以执行您想要的数组。也就是说,它将具有用于数组访问的数据成员(例如 your ),并且它将以某种方式存储您想要访问的索引。我将把细节留给读者(或其他答案),因为这个答案花了很长时间才达到这一点。operator[]std::shared_ptr<float[]>

虽然这个问题被标记为 C++17,但我会注意到在 C++20 中,有范围。具体而言,筛选器视图可用于选择所需的基础视图的元素。例如,您可以为筛选器视图提供一个谓词,当参数减号的地址介于 2 和 5 之间或介于 60 和 65 之间时,该谓词将传递(返回)。我将把细节留给读者,因为这个问题要求 20 岁之前的答案。true_vals