Как работает weak_ptr? - PullRequest
       29

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

57 голосов
/ 15 апреля 2011

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

Спасибо.

Ответы [ 2 ]

93 голосов
/ 15 апреля 2011

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

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

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

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

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

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

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


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

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

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

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

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

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

0 голосов
/ 31 июля 2014

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

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

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

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

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

...