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