Как работает слабый ptr?
Я понимаю, как использовать weak_ptr
и shared_ptr
. Я понимаю, как shared_ptr
работает, подсчитывая количество ссылок в своем объекте. Как это weak_ptr
работы? Я попытался прочитать исходный код boost, и я недостаточно знаком с boost, чтобы понять все, что он использует.
спасибо.
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_ptr
S ссылка на объект счетчика тоже, они тоже должны увеличить "слабый счет".вероятно, еще более интуитивным решением было бы увеличить "слабый счет" для каждого
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", потому что примитивные типы не поддерживают оператор" ->".