Параметр функции process() передается по значению, поэтому аргумент копируется в параметр ptr. Копирование указателя shared_ptr осуществляет инкремент его счетчика ссылок. Таким образом, в функции process() значение счетчика не меньше 2. По завершении функции process() осуществляется декремент счетчика ссылок указателя ptr, но он не может достигнуть нуля. Поэтому, когда локальная переменная ptr удаляется, память, на которую она указывает, не освобождается.

Правильный способ использования этой функции подразумевает передачу ей указателя shared_ptr:

shared_ptr p(new int (42)); // счетчик ссылок = 1

process(p); // копирование p увеличивает счетчик;

            // в функции process() счетчик = 2

int i = *p; // ok: счетчик ссылок = 1

Хотя функции process() нельзя передать встроенный указатель, ей можно передать временный указатель shared_ptr, явно созданный из встроенного указателя. Но это, вероятно, будет ошибкой:

int *x(new int(1024)); // опасно: x - обычный указатель, a

                       // не интеллектуальный process(x);

// ошибка: нельзя преобразовать int* в shared_ptr

process(shared_ptr(x)); // допустимо, но память будет освобождена!

int j = *x; // непредсказуемо: x - потерянный указатель!

В этом вызове функции process() передан временный указатель shared_ptr. Этот временный указатель удаляется, когда завершается выражение, в котором присутствует вызов. Удаление временного объекта приводит к декременту счетчика ссылок, доводя его до нуля. Память, на которую указывает временный указатель, освобождается при удалении временного указателя.

Но указатель x продолжает указывать на эту (освобожденную) область памяти; теперь x — потерянный указатель. Результат попытки использования значения, на которое указывает указатель x, непредсказуем.

При связывании указателя shared_ptr с простым указателем ответственность за эту память передается указателю shared_ptr. Как только ответственность за область памяти встроенного указателя передается указателю shared_ptr, больше нельзя использовать встроенный указатель для доступа к памяти, на которую теперь указывает указатель shared_ptr.

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

Другие операции с указателем shared_ptr

Класс shared_ptr предоставляет также несколько других операций, перечисленных в табл. 12.2 и табл. 12.3. Чтобы присвоить новый указатель указателю shared_ptr, можно использовать функцию reset():

p = new int(1024); // нельзя присвоить обычный указатель

                   // указателю shared_ptr

p.reset(new int(1024)); // ok: p указывает на новый объект

Подобно оператору присвоения, функция reset() модифицирует счетчики ссылок, а если нужно, удаляет объект, на который указывает указатель p. Функцию-член reset() зачастую используют вместе с функцией unique() для контроля совместного использования объекта несколькими указателями shared_ptr. Прежде чем изменять базовый объект, проверяем, является ли владелец единственным. В противном случае перед изменением создается новая копия:

if (!p.unique())

 p.reset(new string(*p)); // владелец не один; резервируем новую копию

*p += newVal; // теперь, когда известно, что указатель единственный,

              // можно изменить объект

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

Упражнение 12.10. Укажите, правилен ли следующий вызов функции process(), определенной в текущем разделе. В противном случае укажите, как его исправить?

shared_ptr p(new int(42));

process(shared_ptr(p));

Упражнение 12.11. Что будет, если вызвать функцию process() следующим образом?

process(shared_ptr(p.get()));

Упражнение 12.12. Используя объявления указателей p и sp, объясните каждый из следующих вызовов функции process(). Если вызов корректен, объясните, что он делает. Если вызов некорректен, объясните почему:

auto p = new int();

auto sp = make_shared();

(a) process(sp);

(b) process(new int());

(c) process(p);

Перейти на страницу:

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