(d) process(shared_ptr(p));

Упражнение 12.13. Что будет при выполнении следующего кода?

auto sp = make_shared();

auto p = sp.get();

delete p;

<p><image l:href="#reader.png"/>12.1.4. Интеллектуальные указатели и исключения</p>

В разделе 5.6.2 упоминалось, что программы, использующие обработку исключений для продолжения работы после того, как произошло исключение, нуждаются в способе правильного освобождения ресурсов в случае исключения. Самый простой из них подразумевает использование интеллектуальных указателей.

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

void f() {

 shared_ptr sp(new int(42)); // зарезервировать новый объект

 // код, передающий исключение, не обрабатываемое в функции f()

} // shared_ptr освобождает память автоматически по завершении функции

При выходе из функции, обычном или в связи с исключением, удаляются все ее локальные объекты. В данном случае указатель 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 для гарантии правильности закрытия соединения.

Использование собственного кода удаления
Перейти на страницу:

Похожие книги