将 std::vector<std::unique_ptr<T>> 移动到 std::vector<std::shared_ptr<T>>。

3

有时候我们会有这样一种情况,我们有一个工厂生产std::unique_ptr向量,后来我们想在类/线程/等之间共享这些指针。因此,最好使用std::shared_ptr。当然,有一种方法可以将std::unique_ptr转换为std::shared_ptr。

std::shared_ptr<int> sharedV;
std::unique_ptr<int> uniqueV(new int(2));

sharedV = std::move(uniqueV);

那么有没有一种简单的方法可以使用标准集合来完成这样的事情呢?

因此最好使用std::shared_ptr。这些东西需要参与所有权吗?如果不需要,您可以通过引用传递向量。 - NathanOliver
@NathanOliver 对于某些特定情况,我们希望共享这些指针的所有权。不要过度热衷 :) - Amadeusz
2个回答

10

你可以使用来自<algorithm>std::move移动一定范围的元素。它的行为与std::copy非常相似,但是是移动元素而不是复制。下面的示例将会把所有在uniqueV中的unique_ptr移动到sharedV中。执行完示例后,uniqueV的元素都将变成nullptr

#include <algorithm>
#include <iterator>
#include <memory>
#include <vector>

int main()
{

    std::vector<std::shared_ptr<int>> sharedV;
    std::vector<std::unique_ptr<int>> uniqueV;

    uniqueV.emplace_back(std::make_unique<int>(42));

    std::move(uniqueV.begin(), uniqueV.end(), std::back_inserter(sharedV));    

    return 0;
}

4
建议使用[比较啰嗦但更高效的]sharedV.insert(sharedV.end(), std::make_move_iterator(uniqueV.begin()), std::make_move_iterator(uniqueV.end())) - Barry
@Barry 我之前不知道 std::make_move_iterator,非常有趣。我不太想将该解决方案附加到我的答案中,我觉得它应该是一个独立的答案。 - François Andrieux
好的,如果你坚持 :) - Barry

9
除了 François Andrieux 的回答std::vector 还有一个可迭代的insert() 成员函数。直接传递您的 unique_ptr 向量的迭代器是行不通的,但有一种方法可以将这些迭代器的反引用从左值转换为X值:std::move_iterator 和相应的工厂函数:std::make_move_iterator
sharedV.insert(sharedV.end(),
    std::make_move_iterator(uniqueV.begin()),
    std::make_move_iterator(uniqueV.end()));

使用insert()可能比使用std::back_inserter更有效的原因是,insert()在前期就知道结果的大小,所以最多只需要进行一次分配,然后在执行插入操作时无需进行大小检查。

为了解决这个问题,建议使用名为extend()的基于范围的重载函数。

template <class T, class Range>
void extend(std::vector<T>& dst, Range&& range) {
    using std::begin; using std::end;
    if constexpr (std::is_lvalue_reference<Range>{}) {
        dst.insert(dst.end(), begin(range), end(range));
    } else {
        dst.insert(dst.end(),
            std::move_iterator(begin(range)), std::move_iterator(end(range)));
    }
}

这是 C++17 的语法,但在 C++14 中也可以轻松实现。只是需要打更多的字。你可以这样编写代码:
extend(sharedV, std::move(uniqueV));

我发现先调整目标向量的大小,然后再使用移动算法(或for循环)更易于阅读。在这种情况下,这样做应该同样有效。 - MikeMB
实现者可以使用back_inserter进行相同的优化,对吗? - GManNickG
@GManNickG 怎么做呢?通过为每个算法添加一个输出迭代器的重载吗?我猜这是可能的,但似乎不是特别实用。 - Barry
@Barry:我在想,如果迭代器是随机访问迭代器,输出迭代器是back_inserter,实现可以额外检查back_inserter中的容器是否可以“reserve”,如果可以,调用“reserve(std::distance(begin, end))”。然后像平常一样继续传递。这应该都可以在编译时完成。浏览了一下我最喜欢的实现,似乎并没有这样做。 - GManNickG
虽然仔细考虑后,move允许的操作可能会在异常方面直接禁止这种情况。无意义地改变容量可能是不可取的。 - GManNickG

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接