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