Ответ 1
Полностью переписывая этот ответ, потому что я совершенно неправильно понял. Это сложно сделать правильно!
Обычная реализация std::weak_ptr
и std::shared_ptr
, которая соответствует стандарту, состоит в том, чтобы иметь два объекта кучи: управляемый объект и блок управления. Каждый общий указатель, который ссылается на один и тот же объект, содержит указатель на объект и блок управления, а также каждый слабый указатель. Контрольный блок хранит запись количества общих указателей и числа слабых указателей и освобождает управляемый объект, когда количество общих указателей достигает 0; сам блок управления освобождается, когда число слабых указателей также достигает 0.
Это осложняется тем фактом, что указатель объекта в общем или слабом указателе может указывать на подобъект реального управляемого объекта, например. базовый класс, член или даже другой объект кучи, который принадлежит управляемому объекту.
S0 ----------______ MO <------+
\__ `----> BC |
\_ _______--------> m1 |
___X__ m2 --> H |
S1 -/ \__ __----------------^ |
\___ _____X__ |
____X________\__ |
W0 /----------------`---> CB -------+
s = 2
w = 1
Здесь у нас есть два общих указателя, указывающих соответственно на базовый класс управляемого объекта и члена, и слабый указатель, указывающий на объект кучи, принадлежащий управляемому объекту; блок управления записывает, что существуют два общих указателя и один слабый указатель. Контрольный блок также имеет указатель на управляемый объект, который он использует для удаления управляемого объекта, когда он истекает.
Семантика owner_before
/owner_less
заключается в сравнении общих и слабых указателей по адресу их блока управления, который гарантированно не изменяется, если сам указатель не изменен; даже если слабый указатель истекает, поскольку все общие указатели были разрушены, его блок управления все еще существует до тех пор, пока все слабые указатели также не будут уничтожены.
Таким образом, ваш код equals
абсолютно корректен и безопасен по потоку.
Проблема заключается в том, что он не согласуется с shared_ptr::operator==
, потому что он сравнивает указатели объектов, а два общих указателя с одним и тем же блоком управления могут указывать на разные объекты (как указано выше).
Для согласованности с shared_ptr::operator==
запись t.lock() == u
будет абсолютно прекрасной; обратите внимание, что если он возвращает true
, то еще не определено, что слабый указатель является слабым указателем другого общего указателя; это может быть указатель псевдонима и поэтому может продолжаться в следующем коде.
Однако сравнение блоков управления имеет меньше затрат (потому что ему не нужно смотреть на блок управления) и даст те же результаты, что и ==
, если вы не используете указатели псевдонимов.
Я думаю, что здесь есть что-то недостающее в стандарте; добавление owner_equals
и owner_hash
позволило бы использовать weak_ptr
в неупорядоченных контейнерах, и, учитывая owner_equals
, на самом деле становится разумным сравнивать слабые указатели на равенство, так как вы можете безопасно сравнивать указатель блока управления, а затем указатель объекта, поскольку если два слабых указателя имеют один и тот же блок управления, то вы знаете, что оба или оба они истекли. Возможно, что-то для следующей версии стандарта.