Сравнение по равенству std :: weak_ptr

Я хочу сравнить два std :: weak_ptr или один std :: weak_ptr и один std :: shared_ptr на равенство.

То, что я хочу знать, является ли объект, на который указывают каждый из точек слабеющего / shared_ptr, то же самое.
Сравнение должно давать отрицательные результаты не только в том случае, если адреса не совпадают, но и в том случае, если базовый объект был удален и затем случайно реконструирован с тем же адресом.

В общем, я хочу, чтобы это утверждение сохранялось, даже если распределитель резервирует тот же адрес:

auto s1 = std::make_shared<int>(43);
std::weak_ptr<int> w1(s1);

s1.reset();

auto s2 = std::make_shared<int>(41);
std::weak_ptr<int> w2(s2);

assert(!equals(w1,w2));

Шаблоны weak_ptr не предоставляют операторов равенства, и, как я понял, это по уважительной причине.

Так что наивная реализация будет выглядеть так:

template <typename T, typename U>
inline bool naive_equals(const std::weak_ptr<T>& t, const std::weak_ptr<U>& u)
{
return !t.expired() && t.lock() == u.lock();
}

template <typename T, typename U>
inline bool naive_equals(const std::weak_ptr<T>& t, const std::shared_ptr<U>& u)
{
return !t.expired() && t.lock() == u;
}

Если срок первого уязвимого времени истек, он возвращает 0. Если нет, я обновляю слабый_отчет до shared_ptr и сравниваю адреса.

Проблема в том, что мне нужно дважды (один раз) заблокировать слабый_птр! Боюсь, это займет слишком много времени.

Я придумал это:

template <typename T, typename U>
inline bool equals(const std::weak_ptr<T>& t, const std::weak_ptr<U>& u)
{
return !t.owner_before(u) && !u.owner_before(t);
}template <typename T, typename U>
inline bool equals(const std::weak_ptr<T>& t, const std::shared_ptr<U>& u)
{
return !t.owner_before(u) && !u.owner_before(t);
}

Который проверяет, не является ли блок-владелец u «перед» t и не перед u, поэтому t == u.

Это работает, как я намереваюсь? Всегда ли два слабых_признака, созданные из разных общих_предприятий, сравниваются как неравные?
Или я что-то пропустил?

Редактировать: Почему я хочу сделать это в первую очередь?
Я хочу иметь контейнер с общими указателями, и я хочу раздавать ссылки на объекты в нем.
Я не могу использовать итераторы, так как они могут быть признаны недействительными. Я мог бы раздавать (целочисленные) идентификаторы, но это приводит к проблемам с уникальностью и требует типа карты и усложняет операции поиска / вставки / удаления.
Идея состоит в том, чтобы использовать std :: set и выдавать сами указатели (заключенные в класс-оболочку) в качестве ключей, чтобы клиенты могли использовать weak_ptr для доступа к объектам в наборе.

27

Решение

Полностью переписываю этот ответ, потому что я совершенно не понял. Это сложная вещь, чтобы получить право!

Обычная реализация 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 на самом деле становится разумным сравнивать слабые указатели на равенство, поскольку вы можете безопасно сравнивать указатель блока управления затем указатель объекта, так как если два слабых указателя имеют один и тот же блок управления, то вы знаете, что либо оба, либо ни один не истек. Возможно, для следующей версии стандарта.

25

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]