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

Другие вопросы по тегам