В чем смысл owner_less, если истекший weak_ptr даст поведение undefined?

Пожалуйста, учтите мою неопытность, но я не понимаю смысла std::owner_less.

Я был показан, что не рекомендуется использовать map с weak_ptr как ключ, потому что истекший weak_ptr ключ сломает карту, на самом деле:

Если он истекает, то порядок контейнера прерывается, и попытка использования контейнера впоследствии даст поведение undefined.

Как undefined это поведение? Причина, о которой я прошу, состоит в том, что docs говорят о owner_less:

Этот функциональный объект предоставляет упорядоченное смешанное поведение на основе владельца (в отличие от основанного на значении) как std:: weak_ptr, так и std:: shared_ptr. Порядок таков, что два смарт-указателя сравнивают эквивалент только в том случае, если они оба пустые или если оба они управляют одним и тем же объектом, даже если значения исходных указателей, полученных методом get(), различны (например, потому что они указывают на разные подобъекты в пределах тот же объект)

Опять же, это моя неопытная речь, но это не похоже на то, что map будет полностью нарушен expired weak_ptr:

Возвращает, является ли объект weak_ptr либо пустым, либо больше нет shared_ptr в принадлежащей ему группе владельца.

Истекшие указатели действуют как пустые объекты weak_ptr при блокировке и, следовательно, больше не могут использоваться для восстановления владельца shared_ptr.

Похоже, он может стать более дряблым, чем полностью undefined. Если одна реализация удаляет истекшие слабые_ptrs и просто не используется или не используется для каких-либо затяжных, когда поведение становится undefined?

Если одна из реализаций не учитывает порядок, но нужен только удобный способ связать weak_ptr с данными, поведение по-прежнему undefined? Другими словами, find начнет возвращать неправильный ключ?

Карта

Единственная проблема, которую я могу найти в документах, - это то, что указано выше, истекшее значение weak_ptrs вернет эквивалент.

В соответствии с этими docs, это не проблема для реализаций, которые не зависят от порядка и не используют для expired weak_ptr s:

Ассоциативный

Элементы ассоциативных контейнеров ссылаются на их ключ, а не на их абсолютное положение в контейнере.

упорядоченную

Элементы в контейнере всегда соблюдают строгий порядок. Все вставленные элементы заданы в этом порядке.

Карта

Каждый элемент связывает ключ с отображаемым значением: Ключи предназначены для идентификации элементов, основным содержанием которых является отображаемое значение.

Похоже, что если реализация не связана с порядком и не имеет использования для expired weak_ptr, тогда нет никакой проблемы, поскольку значения ссылаются на ключ не по порядку, поэтому find с истечением срока действия weak_ptr вернется, возможно, другое значение weak_ptr, но поскольку в этой конкретной реализации нет необходимости использовать его, кроме как erase d, проблем нет.

Я вижу, как может потребоваться использование weak_ptr order or expired weak_ptr, любое приложение, которое может быть, но все поведение кажется далеким от undefined, поэтому a map или set по-видимому, не полностью сломан по истечении срока действия weak_ptr.

Есть ли больше технических объяснений map, weak_ptr и owner_less, которые опровергают эти документы и мою интерпретацию?

Ответ 1

Один пункт разъяснения. Истекшие слабые_ptr не являются UB при использовании owner_less. Из стандартного

в соответствии с отношением эквивалентности, определяемым оператором(),! operator() (a, б) & &! operator() (b, a), два экземпляра shared_ptr или weak_ptr эквивалент тогда и только тогда, когда они разделяют право собственности или оба являются пустыми.

Одна вещь, которую следует помнить, состоит в том, что пустой weak_ptr - это тот, которому никогда не назначался допустимый shared_ptr, или тот, которому был назначен пустой shared_ptr/weak_ptr. Истекший weak_ptr не является пустым слабой_ptr.

Изменить:

Определение, приведенное выше, зависит от того, что означает "пустой" слабый_ptr. Итак, давайте посмотрим на стандартный

  • constexpr weak_ptr() noexcept;

    Эффекты: Создает пустой объект weak_ptr.
     Постусловия: use_count() == 0.

  • weak_ptr (const weak_ptr & r) noexcept;
  • template weak_ptr (const weak_ptr & r) noexcept;
  • template weak_ptr (const shared_ptr & r) noexcept;

    Требуется: второй и третий конструкторы не должны участвовать в разрешении перегрузки, если Y * неявно конвертируется в T *.

    Эффекты: если r пуст, создается пустой объект weak_ptr; в противном случае создается объект weak_ptr, который разделяет владение с помощью r и сохраняет копию указателя, хранящегося в r.

    Постусловия: use_count() == r.use_count().

Обмен просто обменивает содержимое, а назначение определяется как указанные выше конструкторы плюс своп.

Чтобы создать пустой weak_ptr, вы используете конструктор по умолчанию или передаете ему empty_ptr или shared_ptr, который пуст. Теперь вы заметите, что срок действия не означает, что weak_ptr станет пустым. Он просто заставляет его иметь use_count() от нуля и expired(), чтобы вернуть true. Это связано с тем, что базовый счетчик ссылок не может быть освобожден до тех пор, пока не будут освобождены все слабые указатели, разделяющие объект.

Ответ 2

Вот минимальный пример, демонстрирующий ту же проблему:

struct Character
{
    char ch;
};

bool globalCaseSensitive = true;

bool operator< (const Character& l, const Character& r)
{
    if (globalCaseSensitive)
        return l.ch < r.ch;
    else
        return std::tolower(l.ch) < std::tolower(r.ch);
}

int main()
{
    std::set<Character> set = { {'a'}, {'B'} };

    globalCaseSensitive = false; // change set ordering => undefined behaviour
}

map и set требуют, чтобы их ключевой компаратор реализовал строгую слабое упорядочивающее отношение по типу ключа. Это означает, что, помимо прочего, если x меньше y, то x всегда меньше y. Если программа не гарантирует этого, программа демонстрирует поведение undefined.

Мы можем исправить этот пример, предоставив пользовательский компаратор, который игнорирует переключатель чувствительности к регистру:

struct Compare
{
    bool operator() (const Character& l, const Character& r)
    {
        return l.ch < r.ch;
    }
};

int main()
{
    std::set<Character, Compare> set = { {'a'}, {'B'} };

    globalCaseSensitive = false; // set ordering is unaffected => safe
}

Если a weak_ptr истекает, то weak_ptr впоследствии будет по-другому сравнивать с другими из-за того, что он является нулевым и больше не может гарантировать строгое отношение слабого порядка. В этом случае исправление остается неизменным: используйте пользовательский компаратор, который невосприимчив к изменениям в общем состоянии; owner_less является одним из таких компараторов.


Как undefined это поведение?

Undefined - undefined. Нет континуума.

Если одна реализация [...], когда поведение становится undefined?

Как только содержащиеся элементы перестанут иметь четкое строгую слабое упорядочивающее соотношение.

Если одна реализация [...] является поведением еще undefined? Другими словами, find начнет возвращать неправильный ключ?

Undefined поведение не ограничивается просто возвратом неправильного ключа. Он может сделать что угодно.

Это похоже на [...], нет проблем, потому что значения ссылаются на ключ не по порядку.

Без упорядочения ключи не имеют встроенной способности ссылаться на значения.

Ответ 3

std::sort требует упорядочения. owner_less на нем может быть полезно.

В map или set меньше - положив a weak_ptr в качестве ключа к тому, чтобы ухаживать за undefined. Так как вам все равно придется синхронизировать время жизни контейнера и указателя вручную, вы можете также использовать необработанный указатель (или ручной, не владеющий интеллектуальным указателем, который каким-то образом справляется с проблемой истечения срока действия), чтобы сделать это более понятным.