Как работает слабый ptr?


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

спасибо.

2 55

2 ответа:

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

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

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

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

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

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

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


EDIT: теперь, почему они добавляют один к слабому счету вместо того, чтобы просто освободить объект "счетчик", когда оба счета падают до нуля? Лучший вопрос.

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

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

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

вероятно, еще более интуитивным решением было бы увеличить "слабый счет" для каждого shared_ptr, с каждого shared_ptr hold-это ссылка на объект" счетчик".

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

в принципе, "weak_ptr" - это обычный указатель" T*", который позволяет восстановить сильную ссылку, т. е." shared_ptr", позже в коде.

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

в сильной ссылке shared_ptr реализует свою копию, оператор=, конструктор, деструктор и другие соответствующие API для обновления количества ссылок "якоря". Вот как shared_ptr гарантирует, что ваш "T" живет ровно столько, сколько кто-то его использует. В "weak_ptr" те же API просто копируют фактический якорь ptr вокруг. Они не обновляют количество ссылок.

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

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