Как объявить std :: unique_ptr и в чем его польза?

95

Я пытаюсь понять, как это std::unique_ptrработает, и для этого я нашел этот документ. Автор исходит из следующего примера:

#include <utility>  //declarations of unique_ptr
using std::unique_ptr;
// default construction
unique_ptr<int> up; //creates an empty object
// initialize with an argument
unique_ptr<int> uptr (new int(3));
double *pd= new double;
unique_ptr<double> uptr2 (pd);
// overloaded * and ->
*uptr2 = 23.5;
unique_ptr<std::string> ups (new std::string("hello"));
int len=ups->size();

Меня смущает то, что в этой строке

unique_ptr<int> uptr (new int(3));

Мы используем целое число в качестве аргумента (в круглых скобках), и здесь

unique_ptr<double> uptr2 (pd);

мы использовали указатель в качестве аргумента. Какая разница?

Мне также непонятно, чем указатели, объявленные таким образом, будут отличаться от указателей, объявленных "обычным" способом.

Римский
источник
13
new int(3)возвращает указатель на новое int, точно так же, как pdбыл указатель на новое double.
Дэвид Шварц

Ответы:

89

Конструктор unique_ptr<T>принимает необработанный указатель на объект типа T(поэтому он принимает a T*).

В первом примере:

unique_ptr<int> uptr (new int(3));

Указатель является результатом newвыражения, а во втором примере:

unique_ptr<double> uptr2 (pd);

Указатель сохраняется в pdпеременной.

Концептуально ничего не меняется (вы создаете a unique_ptrиз необработанного указателя), но второй подход потенциально более опасен, поскольку он позволяет вам, например, делать:

unique_ptr<double> uptr2 (pd);
// ...
unique_ptr<double> uptr3 (pd);

Таким образом, имеется два уникальных указателя, которые эффективно инкапсулируют один и тот же объект (тем самым нарушая семантику уникального указателя).

Вот почему первая форма для создания уникального указателя лучше, когда это возможно. Обратите внимание, что в C ++ 14 мы сможем:

unique_ptr<int> p = make_unique<int>(42);

Что и понятнее, и безопаснее. Теперь по поводу вашего сомнения:

Мне также непонятно, чем указатели, объявленные таким образом, будут отличаться от указателей, объявленных "обычным" способом.

Умные указатели должны моделировать владение объектом и автоматически заботиться об уничтожении указанного объекта, когда последний (умный, владеющий) указатель на этот объект выпадает из области видимости.

Таким образом, вам не нужно запоминать действия deleteс объектами, размещенными динамически - деструктор интеллектуального указателя сделает это за вас - и не беспокоиться о том, не разыменовать ли (висящий) указатель на объект, который уже был уничтожен:

{
    unique_ptr<int> p = make_unique<int>(42);
    // Going out of scope...
}
// I did not leak my integer here! The destructor of unique_ptr called delete

Теперь unique_ptrэто умный указатель, который моделирует уникальное владение, что означает, что в любой момент в вашей программе должен быть только один (владеющий) указатель на указанный объект - поэтому unique_ptrего нельзя копировать.

Если вы используете интеллектуальные указатели таким образом, чтобы не нарушать неявный контракт, который они требуют от вас соблюдать, у вас будет гарантия, что утечка памяти не произойдет, и будет применяться правильная политика владения вашим объектом. Необработанные указатели не дают вам этой гарантии.

Энди Проул
источник
3
Привет, я ничего не мог понять model object ownership, integer leakв коде или enforcing ownership policy for object. Не могли бы вы предложить темы / ресурсы для изучения этих концепций?
Flame of udun
1
Я не могу использовать unique_ptr, не получив сообщения об ошибке:, The text ">" is unexpected. It may be that this token was intended as a template argument list terminator but the name is not known to be a template.хотя у меня есть #include <utility>и #include <memory>. Любой совет?
Anonymous
15

Нет никакой разницы в работе в обеих концепциях присвоения unique_ptr.

int* intPtr = new int(3);
unique_ptr<int> uptr (intPtr);

похож на

unique_ptr<int> uptr (new int(3));

Здесь unique_ptr автоматически удаляет пространство, занимаемое uptr.


чем указатели, объявленные таким образом, будут отличаться от указателей, объявленных "обычным" способом.

Если вы создаете целое число в пространстве кучи (используя ключевое слово new или malloc ), вам придется очистить эту память самостоятельно (используя соответственно delete или free ).

В приведенном ниже коде

int* heapInt = new int(5);//initialize int in heap memory
.
.//use heapInt
.
delete heapInt;

Здесь вам нужно будет удалить heapInt, когда это будет сделано с помощью. Если его не удалить, то происходит утечка памяти.

Для того , чтобы избежать таких утечек памяти unique_ptr используется, где unique_ptr автоматически удаляет пространство , занимаемое heapInt , когда он выходит из области видимости. Таким образом, вам не нужно удалять или освобождать unique_ptr.

ярость.
источник
10

Уникальные указатели гарантированно уничтожат объект, которым они управляют, когда они выходят за пределы области видимости. http://en.cppreference.com/w/cpp/memory/unique_ptr

В этом случае:

unique_ptr<double> uptr2 (pd);

pdбудет уничтожен, когда uptr2выйдет за рамки. Это облегчает управление памятью за счет автоматического удаления.

Случай unique_ptr<int> uptr (new int(3));не отличается, за исключением того, что необработанный указатель здесь не присваивается никакой переменной.

фатик
источник
-1

Из cppreference одним из std::unique_ptrконструкторов является

явный unique_ptr (указатель p) noexcept;

Итак, чтобы создать новый объект, std::unique_ptrнужно передать указатель на его конструктор.

unique_ptr<int> uptr (new int(3));

Или это то же самое, что

int *int_ptr = new int(3);
std::unique_ptr<int> uptr (int_ptr);

Другое дело, что вам не нужно убирать после его использования. Если вы не используете std::unique_ptr(умный указатель), вам придется удалить его вот так

delete int_ptr;

когда он вам больше не нужен, или это вызовет утечку памяти.

Тевада
источник