Подтвердить что ты не робот

Equality-compare std:: weak_ptr

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

То, что я хочу знать, - это то, является ли объект, каждый из которых имеет значение weak_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;
}

Если первый weak_ptr истек, тем не менее, он дает 0. Если нет, я обновляю weak_ptr до shared_ptr и сравниваю адреса.

Проблема заключается в том, что я должен блокировать weak_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 и t не до u, поэтому t == u.

Это работает так, как я это намерен? Разве два слабых_ptr, созданных из разных shared_ptr, всегда сравниваются как не равные таким образом? Или я что-то пропустил?

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

4b9b3361

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