Так можно ли безопасно использовать unique_ptr в коллекциях stl? - PullRequest
43 голосов
/ 20 мая 2010

Меня смущают философия unique_ptr и rvalue move.

Допустим, у нас есть две коллекции:

std::vector<std::auto_ptr<int>> autoCollection;
std::vector<std::unique_ptr<int>> uniqueCollection;

Теперь я ожидал бы, что следующее не получится, так как нет никакой информации о том, что алгоритм делает внутри, и, возможно, делает внутренние сводные копии и т. П., Таким образом отрывая владение от auto_ptr:

std::sort(autoCollection.begin(), autoCollection.end());

Я понял это. И компилятор справедливо запрещает это.

Но тогда я делаю это:

std::sort(uniqueCollection.begin(), uniqueCollection.end());

И это компилируется. И я не понимаю почему. Я не думал, что unique_ptrs можно скопировать. Означает ли это, что не может быть принято значение поворота, поэтому сортировка менее эффективна? Или этот стержень на самом деле является движением, которое на самом деле так же опасно, как и коллекция auto_ptrs, и должно быть запрещено компилятором?

Я думаю, что мне не хватает какой-то важной информации, поэтому я с нетерпением жду, когда кто-нибудь предоставит мне ага! момент.

Ответы [ 3 ]

52 голосов
/ 21 мая 2010

Я думаю, что это больше вопрос философии, чем техники :) 1001 *

Основной вопрос заключается в том, в чем разница между перемещением и копированием. Я не буду переходить на технический / стандартный язык, давайте сделаем это просто:

  • Копировать: создать другой идентичный объект (или, по крайней мере, тот, который ДОЛЖЕН сравниваться равным)
  • Перемещение: возьмите объект и поместите его в другое место

Как вы сказали, можно реализовать Move в терминах Copy: создать копию в новом месте и удалить оригинал. Однако здесь есть два вопроса. Один из них связан с производительностью, второй - с объектами, используемыми для RAII: кто из них должен иметь право собственности?

Правильный конструктор Move решает 2 проблемы:

  • Понятно, какой объект принадлежит: новый, так как оригинал будет удален
  • Таким образом, нет необходимости копировать указанные ресурсы, что позволяет повысить эффективность

auto_ptr и unique_ptr являются очень хорошей иллюстрацией этого.

С auto_ptr у вас есть семантическое копирование с завинчивающейся копией: оригинал и копия не сравниваются. Вы можете использовать его для семантики Move, но есть риск, что вы потеряете объект, на который указывает куда-либо.

С другой стороны, unique_ptr именно так: он гарантирует уникального владельца ресурса, таким образом избегая копирования и неизбежной проблемы удаления, которая последует. И отсутствие копии гарантируется и во время компиляции. Поэтому он подходит для контейнеров, если вы не пытаетесь инициализировать копию.

typedef std::unique_ptr<int> unique_t;
typedef std::vector< unique_t > vector_t;

vector_t vec1;                           // fine
vector_t vec2(5, unique_t(new Foo));     // Error (Copy)
vector_t vec3(vec1.begin(), vec1.end()); // Error (Copy)
vector_t vec3(make_move_iterator(vec1.begin()), make_move_iterator(vec1.end()));
    // Courtesy of sehe

std::sort(vec1.begin(), vec1.end()); // fine, because using Move Assignment Operator

std::copy(vec1.begin(), vec1.end(), std::back_inserter(vec2)); // Error (copy)

Таким образом, вы можете использовать unique_ptr в контейнере (в отличие от auto_ptr), но ряд операций будет невозможен, поскольку они включают копирование, которое тип не поддерживает.

К сожалению, Visual Studio может быть довольно слабым в применении стандарта, а также имеет ряд расширений, которые вам необходимо отключить, чтобы обеспечить переносимость кода ... не используйте его для проверки стандарта:)

12 голосов
/ 20 мая 2010

unique_ptr перемещаются с помощью своего конструктора перемещения. unique_ptr является подвижным, но не CopyConstructable.

Здесь есть отличная статья о ссылочных значениях здесь . Если вы еще не читали о них или запутались, взгляните!

7 голосов
/ 20 февраля 2013

std::sort может работать только с операциями перемещения и без копирования, если в каждый момент времени существует только одна оперативная копия каждого объекта.Это более слабое требование, чем работа на месте, поскольку в принципе вы можете временно выделить другой массив и переместить все объекты во время переупорядочения.

, например, если std::vector<std::unique_ptr<T>> превышает его емкость, он выделяет хранилище длябольшего вектора, а затем перемещает все объекты из старого хранилища в новое.Это не операция на месте, но она совершенно действительна.

Как оказалось, алгоритмы сортировки, такие как быстрая сортировка и сортировка в куче, на самом деле могут без проблем работать на месте.Подпрограмма секционирования быстрой сортировки использует внутренний std :: swap, который считается операцией перемещения для обоих задействованных объектов.При выборе сводки, один трюк состоит в том, чтобы поменять ее с первым элементом в диапазоне, таким образом, он никогда не будет перемещен, пока не завершится разбиение.

...