Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
Поскольку указатель 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()?
12.1.6. Класс weak_ptr
Класс weak_ptr (табл. 12.5) представляет интеллектуальный указатель, который не контролирует продолжительность существования объекта, на который он указывает. Он только указывает на объект, который контролирует указатель shared_ptr. Привязка указателя weak_ptr к указателю shared_ptr не изменяет счетчик ссылок этого указателя shared_ptr. Как только последний указатель shared_ptr на этот объект будет удален, удаляется и сам объект. Этот объект будет удален, даже если останется указатель weak_ptr на него. Имя weak_ptr отражает концепцию "слабого" совместного использования объекта.
Создаваемый указатель weak_ptr инициализируется из указателя shared_ptr:
auto p = make_shared<int>(42);
weak_ptr<int> wp(p); // wp слабо связан с p; счетчик ссылок p неизменен
Здесь указатели wp и p указывают на тот же объект. Поскольку совместное использование слабо, создание указателя wp не изменяет счетчик ссылок указателя p; это делает возможным удаление объекта, на который указывает указатель wp.
Таблица 12.5. Функции указателя weak_ptr
weak_ptr<T> w Обнуляет указатель weak_ptr, способный указывать на объект типа T weak_ptr<T> w(sp) Указатель weak_ptr на тот же объект, что и указатель sp типа shared_ptr. Тип Т должен быть приводим к типу, на который указывает sp w = p Указатель p может иметь тип shared_ptr или weak_ptr. После присвоения w разделяет собственность с указателем p w.reset() Обнуляет указатель w w.use_count() Возвращает количество указателей shared_ptr, разделяющих собственность с указателем w w.expired() Возвращает значение true, когда функция w.use_count() должна возвратить нуль, и значение false в противном случае w.lock() Возвращает нулевой указатель shared_ptr, если функция expired() должна возвратить значение true; в противном случае возвращает указатель shared_ptr на объект, на который указывает указатель wПоскольку объект может больше не существовать, нельзя использовать указатель weak_ptr для непосредственного доступа к его объекту. Для этого следует вызвать функцию lock(). Она проверяет существование объекта, на который указывает указатель weak_ptr. Если это так, то функция lock() возвращает указатель shared_ptr на совместно используемый объект. Такой указатель гарантирует существование объекта, на который он указывает, по крайней мере, пока существует этот указатель shared_ptr. Рассмотрим пример: