简体   繁体   中英

Move std::vector<std::unique_ptr<T>> to std::vector<std::shared_ptr<T>>

Somtimes there is a case that we have a factory that produces a vector of std::unique_ptr and later on we would like share these pointers between classes/threads/you name it. So it would be desirable to use std::shared_ptr instead. There is a way of course to convert std::uniqe_ptr to std::shared_ptr

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

sharedV = std::move(uniqueV);

So is there any straightforward way to do such thing with std collections?

You can use std::move from <algorithm> to move ranges. It behaves a lot like std::copy , but moves instead. The following example will move all unique_ptr from uniqueV into sharedV . uniqueV 's elements will all be nullptr at the end of the example.

#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;
}

To add on top of François Andrieux's answer , std::vector has a ranged insert() member function. Just passing in the iterators to your vector of unique_ptr won't work, but there's a way to convert the dereferencing of those iterators from lvalues to xvalues: std::move_iterator and its corresponding factory function: std::make_move_iterator :

sharedV.insert(sharedV.end(),
    std::make_move_iterator(uniqueV.begin()),
    std::make_move_iterator(uniqueV.end()));

The reason this may be more efficient than using std::back_inserter is that insert() will know up front what the resulting size will be, so only at most one allocation needs to be done and then none of the actual insertions need to do size checks.

This is so awkward to write that I would suggest range-based overloads of this named 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)));
    }
}

That's C++17, but is easily doable in C++14. Just takes more typing. You would then write:

extend(sharedV, std::move(uniqueV));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM