12.1.5. Класс unique_ptr

Указатель unique_ptr "владеет" объектом, на который он указывает. В отличие от указателя shared_ptr, только один указатель unique_ptr может одновременно указывать на данный объект. Объект, на который указывает указатель unique_ptr, удаляется при удалении указателя. Список функций, специфических для указателя unique_ptr, приведен в табл. 12.4. Функции, общие для обоих указателей, приведены в табл. 12.1.

В отличие от указателя shared_ptr, нет никакой библиотечной функции, подобной функции make_shared(), которая возвращала бы указатель unique_ptr. Вместо этого определяемый указатель unique_ptr связывается с указателем, возвращенным оператором new. Подобно указателю shared_ptr, можно использовать прямую форму инициализации:

unique_ptr<double> p1; // указатель unique_ptr на тип double

unique_ptr<int> p2(new int(42)); // p2 указывает на int со значением 42

Таблица 12.4. Функции указателя unique_ptr (см. также табл. 12.1)

unique_ptr<T> u1 unique_ptr<T, D> u2 Обнуляет указатель unique_ptr, способный указывать на объект типа Т. Указатель u1 использует для освобождения своего указателя оператор delete; а указатель u2 — вызываемый объект типа D unique_ptr<T, D> u(d) Обнуляет указатель unique_ptr, указывающий на объекты типа Т. Использует вызываемый объект d типа D вместо оператора delete u = nullptr Удаляет объект, на который указывает указатель u; обнуляет указатель u u.release() Прекращает контроль содержимого указателя u; возвращает содержимое указателя u и обнуляет его u.reset() u.reset(q) u.reset(nullptr) Удаляет объект, на который указывает указатель u. Если предоставляется встроенный указатель q, то u будет указывать на его объект. В противном случае указатель u обнуляется

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

unique_ptr<string> p1(new string("Stegosaurus"));

unique_ptr<string> p2(p1); // ошибка: невозможно копирование unique_ptr

unique_ptr<string> p3;

p3 = p2; // ошибка: невозможно присвоение unique_ptr

Хотя указатель unique_ptr нельзя ни присвоить, ни скопировать, можно передать собственность от одного (неконстантного) указателя unique_ptr другому, вызвав функцию release() или reset():

// передает собственность от p1 (указывающего на

// строку "Stegosaurus") к p2

unique_ptr<string> p2(p1.release()); // release() обнуляет p1

unique_ptr<string> p3(new string("Trex"));

// передает собственность от p3 к p2

р2.reset(p3.release()); // reset() освобождает память, на которую

                        // указывал указатель p2

Функция-член release() возвращает указатель, хранимый в настоящее время в указателе unique_ptr, и обнуляет указатель unique_ptr. Таким образом, указатель p2 инициализируется указателем, хранимым в указателе p1, а сам указатель p1 становится нулевым.

Функция-член reset() получает необязательный указатель и переустанавливает указатель unique_ptr на заданный указатель. Если указатель unique_ptr не нулевой, то объект, на который он указывает, удаляется. Поэтому вызов функции reset() указателя p2 освобождает память, используемую строкой со значением "Stegosaurus", передает содержимое указателя p3 указателю p2 и обнуляет указатель p3.

Вызов функции release() нарушает связь между указателем unique_ptr и объектом, который он контролирует. Зачастую указатель, возвращенный функцией release(), используется для инициализации или присвоения другому интеллектуальному указателю. В этом случае ответственность за управление памятью просто передается от одного интеллектуального указателя другому. Но если другой интеллектуальный указатель не используется для хранения указателя, возвращенного функцией release(), то ответственность за освобождения этого ресурса берет на себя программа:

p2.release(); // ОШИБКА: p2 не освободит память, и указатель

              // будет потерян

auto p = p2.release(); // ok, но следует не забыть delete(p)

Передача и возвращение указателя unique_ptr

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

unique_ptr<int> clone(int p) {

 // ok: явное создание unique_ptr<int> для int*

 return unique_ptr<int>(new int(p));

}

В качестве альтернативы можно также возвратить копию локального объекта:

unique_ptr<int> clone(int p) {

 unique_ptr<int> ret(new int(p));

 // ...

 return ret;

}

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

Совместимость с прежней версией: класс auto_ptr

Прежние версии библиотеки включали класс auto_ptr, обладавший некоторыми, но не всеми, свойствами указателя unique_ptr. В частности, невозможно было хранить указатели auto_ptr в контейнере и возвращать их из функции.

Хотя указатель auto_ptr все еще присутствует в стандартной библиотеке, вместо него следует использовать указатель unique_ptr.

Передача функции удаления указателю unique_ptr

Подобно указателю shared_ptr, для освобождения объекта, на который указывает указатель unique_ptr, по умолчанию используется оператор delete. Подобно указателю shared_ptr, функцию удаления указателя unique_ptr (см. раздел 12.1.4) можно переопределить. Но по причинам, описанным в разделе 16.1.6, способ применения функции удаления указателем unique_ptr отличается от такового у shared_ptr.

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

// p указывает на объект типа objT и использует объект типа delT

// для его освобождения

// он вызовет объект по имени fcn типа delT

unique_ptr<objТ, delT> p(new objT, fcn);

В качестве несколько более конкретного примера перепишем программу соединения так, чтобы использовать указатель unique_ptr вместо указателя shared_ptr следующим образом:

void f(destination &d /* другие необходимые параметры */) {

 connection c = connect(&d); // открыть соединение

 // когда p будет удален, соединение будет закрыто

 unique_ptr<connection, decltype(end_connection)*>

  p(&с, end_connection);

 // использовать соединение

 // по завершении f(), даже при исключении, соединение будет

 // закрыто правильно

}

Для определения типа указателя на функцию используется ключевое слово decltype (см. раздел 2.5.3). Поскольку выражение decltype(end_connection) возвращает тип функции, следует добавить символ *, указывающий, что используется указатель на этот тип (см. раздел 6.7).

Упражнения раздела 12.1.5

Упражнение 12.16. Компиляторы не всегда предоставляют понятные сообщения об ошибках, если осуществляется попытка скопировать или присвоить указатель unique_ptr. Напишите программу, которая содержит эти ошибки, и посмотрите, как компилятор диагностирует их.

Упражнение 12.17. Какие из следующих объявлений указателей unique_ptr недопустимы или вероятнее всего приведут к ошибке впоследствии? Объясните проблему каждого из них.

int ix = 1024, *pi = &ix, *pi2 = new int(2048);

typedef unique_ptr<int> IntP;

(a) IntP p0(ix);            (b) IntP p1(pi);

(c) IntP p2(pi2);           (d) IntP p3(&ix);

(e) IntP p4(new int(2048)); (f) IntP p5(p2.get());

Упражнение 12.18. Почему класс указателя shared_ptr не имеет функции-члена release()?