简体   繁体   中英

How to insert several unique_ptrs into vector at once

I have a vector:

std::vector<std::unique_ptr<int>>

and would like to insert several new unique_ptr<int> 's into it at a specified location. There is the member function std::vector::insert(iterator position, size_type n, const value_type& val) but alas, the restrictions on copying unique_ptr 's does not allow the use of this overload.

I have read this question , however that is for inserting unique_ptr 's that already exist in another vector. I want to create new ones.

I realize I can do it with a loop, for example to insert 3 new items to the beginning of the vector:

for (int n = 0; n != 3; ++n)
   vec.insert(vec.begin(), std::make_unique<int>(0));

However I'm wondering if there's a cleaner way to do this, and possibly one that allocates the new memory up-front.

Edit for clarification: the number of items to add to the vector is completely arbitrary - I wrote 3 in my example code but it could be any value and not necessarily one that's known at compile time.

There is two different kinds of memory being allocated here. There is the memory allocated in the vector for the unique_ptr itself (which will not be very much, just a pointer per unique_ptr). And then the dynamically allocated memory for the object managed by each unique_ptr .

You cannot allocate all the memory up-front as the dynamically allocated memory for the object within each unique_ptr must be allocated separately.

But if you want to avoid reallocations of the memory for the vector due to multiple insert calls you could do a reserve first:

vec.reserve(vec.size() + n);

I doubt it will have any impact for n as small as 3 though.

More of an issue is that for each insert the vector has to move all the contents of the vector after the insert point along by one. Moving unique_ptr is cheap but it could add-up.

It is certainly not cleaner, but you could do the moving yourself once using std::move_backward . Resize the vector to the size required, move all the elements along and then move-assign the unique_ptr in the elements where you want to insert:

auto prev_size = vec.size();
vec.resize(prev_size + 3);
auto prev_end = vec.begin() + prev_size;
std::move_backward(vec.begin(), prev_end, vec.end());
for (int n = 0; n != 3; ++n)
  vec[n] = std::make_unique<int>(0);

Another, perhaps cleaner, way of achieving the same thing is to create your own custom forward-iterator to use in the std::vector::insert(const_iterator position, InputIterator first, InputIterator last); overload of insert :

template<typename T>
struct UniquePtrInserter : std::iterator< 
                           std::forward_iterator_tag,
                           std::unique_ptr<T>,
                           std::ptrdiff_t,  
                           const std::unique_ptr<T>*,
                           std::unique_ptr<T>>{
  int n_;
public:
  explicit UniquePtrInserter<T>(int n = 0) : n_(n) {}
  UniquePtrInserter<T>& operator++() {n_++; return *this;}
  bool operator==(UniquePtrInserter<T> other) const {return n_ == other.n_;}
  bool operator!=(UniquePtrInserter<T> other) const {return !(*this == other);}
  std::unique_ptr<T> operator*() const {return std::make_unique<T>(); }
};

vec.insert(vec.begin(), UniquePtrInserter<int>(0), UniquePtrInserter<int>(3));

If you know at compile-time how many pointers you want to put in the vector, you can use a function like the following one:

#include<vector>
#include<memory>
#include<cstddef>

template<std::size_t... I>
constexpr auto gen(std::index_sequence<I...>) {
    std::vector<std::unique_ptr<int>> vec;
    int arr[] = { (vec.push_back(std::make_unique<int>(0)), I)... };
    (void)arr;
    return vec;
}

template<std::size_t N>
constexpr auto create() {
    return gen(std::make_index_sequence<N>{});
}

int main() {
    auto vec = create<3>();
}

The cleanest way to do this is to use a move iterator to move the elements, for example:

#include <iostream>
#include <vector>
#include <memory>
#include <iterator>

using T = std::unique_ptr<int>;

void print_vec(const std::vector<T>& vec)
{
    for (auto& x: vec) {
         std::cout << ' ' << *x;
    }
    std::cout << '\n';
}

template <typename V, typename ...X>
void emplace(V& v, typename V::const_iterator i, X&&... x) {
    T a[] = { std::forward<X>(x)... };
    v.insert(i, std::make_move_iterator(std::begin(a)), std::make_move_iterator(std::end(a)));
}

int main ()
{
    std::vector<T> vec;
    emplace(vec, vec.begin(), std::make_unique<int>(601), std::make_unique<int>(602), std::make_unique<int>(603));
    print_vec(vec);    
    emplace(vec, std::next(vec.begin()), std::make_unique<int>(501), std::make_unique<int>(502), std::make_unique<int>(503));
    print_vec(vec);

}

I've provided a simple wrapper function which takes a sequence (as variadic argument sequence) and inserts this into the vector using a move_iterator .

But you can use the move_iterator to move from any container that will support the move operator.

You can use generate_n to avoid the raw loop

std::vector<std::unique_ptr<int>> vec;
generate_n( back_inserter(vec), 3, []() { return make_unique<int>(); });

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