Подтвердить что ты не робот

Как работает weak_ptr?

Я понимаю, как использовать weak_ptr и shared_ptr. Я понимаю, как работает shared_ptr, подсчитывая количество ссылок в его объекте. Как работает weak_ptr? Я пробовал читать через исходный код boost, и я недостаточно осведомлен о том, чтобы понять все, что он использует.

Спасибо.

4b9b3361

Ответ 1

shared_ptr использует дополнительный объект "счетчик" (например, "общий счет" или "блок управления" ) для хранения счетчика ссылок. (BTW: этот объект "счетчик" также сохраняет дебетер.)

Каждый shared_ptr и weak_ptr содержит указатель на фактический указатель и второй указатель на объект "counter" .

Чтобы реализовать weak_ptr, объект "counter" хранит два разных счетчика:

  • "Use count" - количество экземпляров shared_ptr, указывающих на объект.
  • "слабый счет" - это число экземпляров weak_ptr, указывающих на объект, плюс один, если "счетчик использования" по-прежнему > 0.

Указатель удаляется, когда "счетчик использования" достигает нуля.

Вспомогательный объект "счетчик" удаляется, когда "слабый счет" достигает нуля (что означает, что "счетчик использования" также должен быть равен нулю, см. выше).

Когда вы пытаетесь получить shared_ptr из weak_ptr, библиотека атомарно проверяет "счет использования", а если он > 0 увеличивает его. Если это удастся, вы получите свой shared_ptr. Если "счетчик использования" уже равен нулю, вместо этого вы получите пустой экземпляр shared_ptr.


РЕДАКТИРОВАТЬ: почему они добавляют один к слабеньему счету вместо того, чтобы просто отпускать объект "counter" , когда оба значения падают до нуля? Хороший вопрос.

Альтернативой может быть удаление объекта "counter" , когда "счетчик использования" и "слабый счет" упадут до нуля. Здесь первая причина: проверка двух (указательных размеров) счетчиков атомарно не возможна на каждой платформе, и даже там, где она есть, это сложнее, чем проверка только одного счетчика.

Другая причина заключается в том, что истец должен оставаться действительным до тех пор, пока он не завершит выполнение. Поскольку удаленный объект хранится в объекте "счетчик" , это означает, что объект "счетчик" должен оставаться действительным. Подумайте, что может произойти, если один объект shared_ptr и один weak_ptr для какого-либо объекта, и они являются reset одновременно в параллельных потоках. Пусть говорят, что shared_ptr на первом месте. Он уменьшает "счет использования" до нуля и начинает выполнять дебетер. Теперь weak_ptr уменьшает "слабый счет" до нуля и находит, что "счетчик использования" также равен нулю. Таким образом, он удаляет объект "counter" , а вместе с ним и дебетер. Пока дебетер все еще работает.

Конечно, были бы разные способы гарантировать, что объект "счетчик" останется в живых, но я думаю, что увеличение "слабого подсчета" на одном является очень элегантным и интуитивным решением. "Слабый счет" становится счетчиком ссылок для объекта "счетчик" . И так как shared_ptr ссылается на объект-счетчик, они тоже должны увеличивать "слабый счет" .

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

Добавление одного для всех экземпляров shared_ptr - это просто оптимизация (сохраняет одно атомное приращение/уменьшение при копировании/назначении экземпляров shared_ptr).

Ответ 2

В принципе, "weak_ptr" является обычным указателем "T *", который позволяет вам ВОССТАНОВИТЬ сильную ссылку, то есть "shared_ptr", позже в коде.

Как обычный T *, weak_ptr не делает никакого подсчета ссылок. Внутренне, чтобы поддерживать подсчет ссылок на произвольном типе T, STL (или любая другая библиотека, реализующая такой тип логики) создает объект-оболочку, который мы будем называть "Якорь" . "Якорь" существует только для того, чтобы реализовать счетчик ссылок и "когда количество нулевых значений, вызванное вызовом", нам нужно.

В сильной ссылке shared_ptr реализует свой экземпляр copy, operator =, constructor, destructor и другие соответствующие API для обновления счетчика ссылок "Anchor". Вот как shared_ptr гарантирует, что ваш "Т" живет ровно столько, сколько кто-то его использует. В "weak_ptr" те же API-интерфейсы просто копируют фактический привязку якоря. Они НЕ обновляют количество ссылок.

Вот почему наиболее важные API-интерфейсы "weak_ptr" "истекли" и плохо называемый "блокировка". "Истек срок действия" говорит вам, находится ли базовый объект по-прежнему, т.е. "Он уже удалился сам, потому что все сильные ссылки вышли из сферы действия?". "Блокировка" (если это возможно) преобразует weak_ptr в сильную ссылку shared_ptr, восстанавливая подсчет ссылок.

Кстати, "lock" - ужасное имя для этого API. Вы не (просто) вызываете мьютекс, вы создаете сильную ссылку от слабого, с тем, что действует "Якорь" . Самым большим недостатком обоих шаблонов является то, что они не реализовали оператор- > , поэтому, чтобы что-либо сделать с вашим объектом, вы должны восстановить исходный "T *". Они делали это главным образом для поддержки таких вещей, как "shared_ptr", потому что примитивные типы не поддерживают оператор "- > ".