Подтвердить что ты не робот

Можно ли безопасно использовать unique_ptr в коллекциях stl?

Я путаюсь с идеями 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, и должен быть запрещен компилятором?

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

4b9b3361

Ответ 1

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

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

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

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

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

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

Символы 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 может быть довольно слабым в применении стандарта, а также имеет ряд расширений, которые вам необходимо отключить, чтобы обеспечить переносимость кода... не используйте его для проверки стандарта:)

Ответ 2

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

Там есть отличная статья о ссылках raleue здесь. Если вы еще не читали о них или не смущены, посмотрите!

Ответ 3

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

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

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