Как построить вектор с уникальными указателями

Я пытаюсь построить вектор с unique_ptr. Но я не нахожу прямого пути. Следующий код не компилируется. Ошибка: вызов неявно удаленного конструктора копирования 'std::__1::unique_ptr >':

#include <iostream>
#include <memory>
#include <utility>
#include <vector>
class test1{
public:
    test1(){};
    test1(test1&&)=default;
};

int main(int argc, const char * argv[]) {
    std::unique_ptr<test1> us(new test1());
    std::vector<std::unique_ptr<test1>> vec{move(us)};
    return 0;
}

person EckhardN    schedule 30.04.2015    source источник


Ответы (2)


Эта make_vector представляет собой функцию, которая принимает любое количество аргументов и идеально передает их в вектор.

// get the first type in a pack, if it exists:
template<class...Ts>
struct first {};
template<class T, class...Ts>
struct first<T,Ts...>{
  using type=T;
};
template<class...Ts>
using first_t=typename first<Ts...>::type;

// build the return type:
template<class T0, class...Ts>
using vector_T = 
  typename std::conditional<
    std::is_same<T0, void>::value,
    typename std::decay<first_t<Ts...>>::type,
    T0
  >::type;
template<class T0, class...Ts>
using vector_t = std::vector< vector_T<T0, Ts...> >;

// make a vector, non-empty arg case:
template<class T0=void, class...Ts, class R=vector_t<T0, Ts...>>
R make_vector( Ts&&...ts ) {
  R retval;
  retval.reserve(sizeof...(Ts)); // we know how many elements
  // array unpacking trick:
  using discard = int[];
  (void)discard{0,((
    retval.emplace_back( std::forward<Ts>(ts) )
  ),void(),0)...};
  return retval; // NRVO!
}
// the empty overload:
template<class T>
std::vector<T> make_vector() {
  return {};
}

использовать:

std::vector<std::unique_ptr<test1>> vec =
  make_vector(
    std::move(u1), std::move(u2)
  );

живой пример

Я его немного отполировал. Он выведет возвращаемый тип, если вы передадите ему 1 или более аргументов и не передадите ему тип. Если вы передадите ему тип, он будет использовать этот тип. Если вы не передадите ему тип или какие-либо аргументы, он будет жаловаться. (если вы пересылаете пакеты или храните их в определенном типе, я всегда даю ему тип).


Можно сделать еще один шаг, где мы делаем вывод типа возвращаемого значения, чтобы исключить требование указывать тип даже в пустом случае. Это может потребоваться в вашем случае использования, я не знаю, но это соответствует тому, что вам не нужно указывать тип {}, поэтому я подумал, что выброшу его:

template<class...Ts>
struct make_vec_later {
  std::tuple<Ts...> args; // could make this `Ts&&...`, but that is scary

   // make this && in C++14
  template<class T, size_t...Is>
  std::vector<T> get(std::index_sequence<Is...>) {
    return make_vector<T>(std::get<Is>(std::move(args))...);
  }

  // make this && in C++14
  template<class T>
  operator std::vector<T>(){
    return std::move(*this).template get<T>( std::index_sequence_for<Ts...>{} );
  }
};
template<class...Ts>
make_vec_later<Ts...> v(Ts&&...ts) {
  return {std::tuple<Ts...>(std::forward<Ts>(ts)...)};
}

это зависит от функции index_sequence С++ 14, но ее легко переписать на С++ 11, если ваш компилятор еще не имеет ее. Просто погуглите о переполнении стека, существует множество реализаций.

Теперь синтаксис выглядит так:

std::vector<std::unique_ptr<test1>> vec =
  v(std::move(u1));

где список аргументов может быть пустым.

Живой пример

Поддержка распределителей вариантов остается на усмотрение пользователя. Добавьте к make_vector еще один тип с именем A и установите по умолчанию void. Если он недействителен, замените его на std::allocator<T> для любого типа T, выбранного для вектора. В версии вывода возвращаемого типа сделайте что-то подобное.

person Yakk - Adam Nevraumont    schedule 30.04.2015
comment
Есть ли подобное решение для карт? - person EckhardN; 11.05.2015
comment
@eckhardn вам не нужен уникальный ключ-указатель на карте или в наборе. В противном случае, конечно, вам просто нужно либо передавать попарно и менять возвращаемые типы, либо делать что-то слишком сложное с varargs. - person Yakk - Adam Nevraumont; 11.05.2015
comment
Мне нужен ключ без знака и уникальный указатель в качестве значения. Я пытался изменить ваш код для карт, но не получилось. - person EckhardN; 11.05.2015

Вы вызываете конструктор vector ((7) на связанной странице ), который принимает аргумент initializer_list<T>. initializer_list разрешает только const доступ к своим элементам, поэтому vector должен копировать элементы, и это, конечно, не компилировать.

Следующее должно работать

std::unique_ptr<test1> us(new test1());
std::vector<std::unique_ptr<test1>> vec;

vec.push_back(move(us));
// or
vec.push_back(std::unique_ptr<test1>(new test1()));
// or
vec.push_back(std::make_unique<test1>()); // make_unique requires C++14

Вы можете использовать конструктор vector, который принимает два итератора, но решение по-прежнему не является однострочным, поскольку оно требует от вас определения временного массива, из которого вы затем можете перемещаться.

std::unique_ptr<test1> arr[] = {std::make_unique<test1>()};
std::vector<std::unique_ptr<test1>> vec{std::make_move_iterator(std::begin(arr)),
                                        std::make_move_iterator(std::end(arr))};
person Praetorian    schedule 30.04.2015
comment
В моей ситуации я не могу использовать функцию push_back. Мне нужен конструктор из-за рекурсии. Я правильно понимаю, что нет возможности использовать конструктор? - person EckhardN; 30.04.2015
comment
vec.emplace_back также невозможен для моей проблемы - person EckhardN; 30.04.2015
comment
@ 0x499602D2 Небезопасно для исключений, если emplace_back выбросит, произойдет утечка.\ - person Praetorian; 30.04.2015
comment
Как и ваш пример. Почему не push_back(make_shared<test1>())? - person 0x499602D2; 30.04.2015
comment
@EckhardN Я не могу придумать другого способа построить vector, передав право собственности на существующий unique_ptr. Можете ли вы опубликовать пример, показывающий проблему, с которой вы столкнулись? Возможно, есть другой обходной путь - person Praetorian; 30.04.2015
comment
Я пишу генератор кода, в котором мне нужно создать объект без дополнительного кода, такого как использование push_back. Я зависим от конструктора. - person EckhardN; 30.04.2015
comment
@ 0x499602D2 Что означает мой пример? Во втором случае конструкция unique_ptr гарантированно будет завершена до вызова push_back. push_back(make_shared<test1>()) не будет компилироваться, и я не упомянул push_back(make_unique<test1>()), потому что вопрос помечен как C++11, но я все равно добавлю это. - person Praetorian; 30.04.2015
comment
@EckhardN Почему нельзя добавить строку для вызова push_back? Если вы не добавите пример, мне трудно предложить решение. - person Praetorian; 30.04.2015
comment
@Praetorian Я строю дерево объектов, где у меня возникла идея сделать что-то вроде: std::vector‹std::vector‹..››foo{{{unique_ptr,unique_ptr,..},{unique_ptr,unique_ptr ,...}...}} - person EckhardN; 30.04.2015
comment
@EckhardN Добавлен еще один вариант, но он, вероятно, тоже не соответствует вашим требованиям. Извините, я не думаю, что то, что вы хотите сделать, возможно в такой форме. - person Praetorian; 30.04.2015
comment
@PraetorianСпасибо! Да, временный массив не является возможным решением. Поэтому я должен найти совершенно другое решение. Я напишу n-функций с параметрами от 1 до n, которые будут строить возвращаемый вектор. Не элегантный, но лучший, о котором я думаю. - person EckhardN; 30.04.2015
comment
@EckhardN Если у вас есть пример кода, я уверен, что мы, вероятно, сможем найти решение, которое не требует использования N перегрузок. - person T.C.; 30.04.2015