Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
При выходе из функции, обычном или в связи с исключением, удаляются все ее локальные объекты. В данном случае указатель sp имеет тип shared_ptr, поэтому при удалении проверяется его счетчик ссылок. В данном случае sp — единственный указатель на контролируемую им область памяти, поэтому она освобождается в ходе удаления указателя sp.
Память, контролируемая непосредственно, напротив, не освобождается автоматически, когда происходит исключение. Если для управления памятью используются встроенные указатели и исключение происходит после оператора new, но перед оператором delete, то контролируемая память не будет освобождена:
void f() {
int *ip = new int(42); // динамически зарезервировать новый объект
// код, передающий исключение, не обрабатываемое в функции f()
delete ip; // освобождает память перед выходом
}
Если исключение происходит между операторами new и delete и не обрабатывается в функции f(), то освободить эту память никак не получится. Вне функции f() нет указателя на эту память, поэтому нет никакого способа освободить ее.
Интеллектуальные указатели и классы без деструкторовБольшинство классов языка С++, включая все библиотечные классы, определяют деструкторы (см. раздел 12.1.1), заботящиеся об удалении используемых объектом ресурсов. Но не все классы таковы. В частности, классы, разработанные для использования и в языке С, и в языке С++, обычно требуют от пользователя явного освобождения всех используемых ресурсов.
Классы, которые резервируют ресурсы, но не определяют деструкторы для их освобождения, подвержены тем же ошибкам, которые возникают при самостоятельном использовании динамической памяти. Довольно просто забыть освободить ресурс. Аналогично, если произойдет исключение после резервирования ресурса, но до его освобождения, программа потеряет его.
Для управления классами без деструкторов зачастую можно использовать те же подходы, что и для управления динамической памятью. Предположим, например, что используется сетевая библиотека, применимая как в языке С, так и в С++. Использующая эту библиотеку программа могла бы содержать такой код:
struct destination; // представляет то, с чем установлено соединение
struct connection; // информация для использования соединения
connection connect(destination*); // открывает соединение
void disconnect(connection); // закрывает данное соединение
void f(destination &d /* другие параметры */) {
// получить соединение; не забыть закрывать по завершении
connection с = connect(&d); // использовать соединение
// если забыть вызывать функцию disconnect() перед выходом из
// функции f(), то уже не будет никакого способа закрыть соединение
}
Если бы у структуры connection был деструктор, то по завершении функции f() он закрыл бы соединение автоматически. Однако у нее нет деструктора. Эта проблема почти идентична проблеме предыдущей программы, использовавшей указатель shared_ptr, чтобы избежать утечек памяти. Здесь также можно использовать указатель shared_ptr для гарантии правильности закрытия соединения.
Использование собственного кода удаленияПо умолчанию указатели shared_ptr подразумевали, что они указывают на динамическую память. Следовательно, когда указатель shared_ptr удаляется, он по умолчанию выполняет оператор delete для содержащегося в нем указателя. Чтобы использовать указатель shared_ptr для управления соединением connection, следует сначала определить функцию, используемую вместо оператора delete. Должна быть возможность вызова этой функции удаления (deleter) с указателем, хранимым в указателе shared_ptr. В данном случае функция удаления должна получать один аргумент типа connection*:
void end_connection(connection *p) { disconnect(*p); }
При создании указателя shared_ptr можно передать необязательный аргумент, указывающий на функцию удаления (см. раздел 6.7):
void f(destination &d /* другие параметры */) {
connection с = connect(&d);
shared_ptr<connection> p(&c, end_connection);
// использовать соединение
// при выходе из функции f(), даже в случае исключения, соединение
// будет закрыто правильно
}
При удалении указателя p для хранимого в нем указателя вместо оператора delete будет вызвана функция end_connection(). Функция end_connection(), в свою очередь, вызовет функцию disconnect(), гарантируя таким образом закрытие соединения. При нормальном выходе из функции f() указатель p будет удален в ходе процедуры выхода. Кроме того, указатель p будет также удален, а соединение закрыто, если произойдет исключение.
Внимание! Проблемы интеллектуального указателяИнтеллектуальные указатели могут обеспечить безопасность и удобство работы с динамически созданной памятью только при правильном использовании. Для этого следует придерживаться ряда соглашений.
• Не используйте значение того же встроенного указателя для инициализации (переустановки) нескольких интеллектуальных указателей.
• Не используйте оператор delete для указателя, возвращенного функцией get().
• Не используйте функцию get() для инициализации или переустановки другого интеллектуального указателя.
• Используя указатель, возвращенный функцией get(), помните, что указатель станет недопустимым после удаления последнего соответствующего интеллектуального указателя.
• Если интеллектуальный указатель используется для управления ресурсом, отличным от области динамической памяти, зарезервированной оператором new, не забывайте использовать функцию удаления (раздел 12.1.4 и раздел 12.1.5).
Упражнения раздела 12.1.4Упражнение 12.14. Напишите собственную версию функции, использующую указатель shared_ptr для управления соединением.
Упражнение 12.15. Перепишите первое упражнение так, чтобы использовать лямбда-выражение (см. раздел 10.3.2) вместо функции end_connection().
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 владеет объектом, на который указывает, он не поддерживает обычного копирования и присвоения: