Почему нет оператора‹ для класса std::weak_ptr?

Я использую слабые указатели в качестве ключей на карте. Однако, когда я попытался скомпилировать, я получил уродливые сообщения, которые я интерпретировал как означающие, что мне не хватает оператора сравнения для std::weak_ptr, который, очевидно, требуется в std::map, так как он упорядочивает свои элементы в соответствии с ключевое значение.

Однако теперь класс weak_ptr является классом типа интеллектуального указателя и, как таковой, работает с указателями на некоторые управляемые данные.

Есть ли веская причина, по которой этот класс НЕ предоставляет базовую реализацию метода оператора? Я имею в виду, что сравнение значений указателя кажется мне довольно очевидным, и, если требуется, чтобы он работал по-другому, тогда нужно иметь возможность расширить или переопределить метод, чтобы вместо этого получить ожидаемое поведение. т это?

Ваше понимание будет высоко оценено здесь. Спасибо в ожидании.


person Kzwix    schedule 09.04.2018    source источник
comment
Учитывая, что базовый объект слабого указателя может быть уничтожен в любое время, сравнение слабых указателей совершенно бессмысленно. Пикосекунда после того, как < закончит сравнение указателей, один из базовых объектов может быть уничтожен, так что результаты ваших сравнений. Это не имеет ничего общего со сравнением указателей в целом, как утверждают ответы. std::less обеспечивает обязательный слабый порядок указателей. Поищи это. Нет ничего плохого в сравнении обычных указателей. Но сравнение слабых указателей по своей природе совершенно бессмысленно.   -  person Sam Varshavchik    schedule 09.04.2018
comment
Хорошо, спасибо. Однако, насколько я понимаю, слабый указатель имеет указатель на структуру держателя (вещь, которая подсчитывает количество умных указателей, использующих его, и которая содержит реальный указатель) соответствующего экземпляра (семейства) shared_ptr, верно? Таким образом, не должно быть так сложно использовать исходный адрес для сравнения, даже если количество экземпляров упало до 0 - если только реализация не удалит эту самую внутреннюю структуру, когда срок действия указателя истек (я не знаю, я не проверял внутренний код для STL.Может быть, я должен)   -  person Kzwix    schedule 09.04.2018


Ответы (3)


std::owner_less — это правильный способ упорядочивания интеллектуальных указателей в качестве ключей на картах.

#include <map>
#include <memory>
#include <string>

struct Foo
{
};

using my_key = std::weak_ptr<Foo>;
using my_comp = std::owner_less<my_key>;

int main()
{
    auto m = std::map<my_key, std::string, my_comp>();

    auto p = std::make_shared<Foo>();
    auto p1 = std::make_shared<Foo>();

    m.emplace(p, "foo");
    m.emplace(p1, "bar");

    p.reset();
    p1.reset();
}
person Richard Hodges    schedule 09.04.2018
comment
Я не знал об этом. Хорошая находка :) - person rubenvb; 09.04.2018
comment
Я тоже не знал об этом. Однако мне трудно понять, в каких случаях указатели внутри могут быть разными. В документации, которую я видел о std::owner_less (или о std::weak_ptr.owner_before), говорится, что он сравнивает собственный указатель независимо от результата get(), и утверждает, что они могут отличаться (например, потому что они указывают на разные подобъекты). внутри одного и того же объекта) Как это будет работать? Я имею в виду, если у меня есть умные указатели на класс Foo, как один из них будет указывать на другой подобъект? - person Kzwix; 09.04.2018
comment
@Kzwix есть конструктор shared_ptr, который позволяет вам разделить время жизни контролируемого объекта, но указать на его подобъект. См. примечание 8 на этой странице: en.cppreference.com/w/cpp/memory /shared_ptr/shared_ptr - person Richard Hodges; 10.04.2018
comment
Спасибо, Ричард, ты заставил меня открыть для себя еще одну вещь. Однако в документации сказано, что ожидается указатель на element_type. И element_type определен в ссылке как сам тип T (поскольку мы еще не в C++17). Однако в описании конструктора явно указано, что этот указатель должен быть тем, что мы хотим, если он гарантированно будет активным, пока существует общий указатель. Следовательно, указатель на поле или на пониженную версию, что угодно. Но интересно, если в моем классе Foo есть поле типа Bar, как я могу предоставить Bar*, если он ожидает Foo*? - person Kzwix; 10.04.2018

ОБНОВЛЕНИЕ: существует четко определенный способ сравнения std::weak_ptr, см. @Richard answer. Я оставлю свой ответ для исторических архивных целей.


Реализация хорошего operator< потребует создания shared_ptr из weak_ptr и вызова его operator<. Это

  1. "дорогая" операция
  2. undefined, когда базовый shared_ptr больше не существует.

Было бы трудно получить четко определенный и эффективный порядок weak_ptr в целом.

Обратите внимание, что вы всегда можете передать пользовательский компаратор объекту карты, который вы можете реализовать любым способом и оставить локальным только для этого объекта. Но тогда это зависит от вас, чтобы найти хороший способ сделать это. Возможно, вы могли бы использовать указатель на блок управления для подключенного shared_ptr, но это детали реализации, к которым вы не можете получить доступ. Поэтому я действительно не вижу никакого разумного способа сделать это.

person rubenvb    schedule 09.04.2018
comment
Не было бы UB сравнивать значения указателя (а не значения указателя), что, я думаю, именно то, что хочет сделать OP. Настоящая причина, по которой operator< не определена, заключается в том, что это может привести к неверным результатам, а не в том, что это UB. (В частности, с несуществующими shared_ptr можно легко справиться; это то, что делает std::owner_less.) - person Konrad Rudolph; 09.04.2018
comment
@KonradRudolph это может быть не определено в некоторых системах (и везде определено реализацией), см. DR 1438. - person Jonathan Wakely; 09.04.2018

Я думаю, что ключевым моментом является то, что (цитата из C++ Concurrency in Действие, стр. 194, выделение мое)

Использование таких указателей опасности основано на том факте, что безопасно использовать значение указателя после того, как объект, на который он ссылается, был удален. Это технически неопределенное поведение, если вы используете реализацию new и delete по умолчанию, поэтому либо вам нужно убедиться, что ваша реализация разрешает это, либо вам нужно использовать настраиваемый распределитель, который разрешает такое использование.

Предоставление operator<() требует чтения необработанного значения указателя, что может привести к неопределенному поведению (т. е. когда expired() возвращает true). Практически невозможно гарантировать, что сравнение прошло успешно, если только вы сначала не lock() не проверите, что возвращаемое значение не равно нулю.

person Lingxi    schedule 09.04.2018
comment
Автор этой книги сообщил об дефекте в соответствующей формулировке, и теперь стандарт ясно дает понять, что сравнение недопустимого указателя values ​​определяется реализацией, а не обязательно не определено. Но это все равно затруднит weak_ptr определение сравнения. - person Jonathan Wakely; 09.04.2018