Как получить неконстантный верхний элемент из priority_queue с определяемыми пользователем объектами?

std::priority_queue::top возвращает постоянное значение. Однако я хотел бы удалить верхний элемент из очереди приоритетов и иметь возможность изменить его где-то еще.

priority_queue<SomeClass, vector<SomeClass>, SomeClassCompare > pQueue;
...
SomeClass *toBeModified = &(pQueue.top());
pQueue.pop();
toBeModified->setMember(3); // I would like to do this

Есть ли способ, которым я могу взять верхний элемент из приоритетной очереди (и удалить из очереди) и изменить его, как я хочу?

Ответ 1

Стандартные контейнеры и контейнерные адаптеры имеют семантику значений. Когда вы вставляете элемент в очередь, создается копия. Когда вы удаляете объект из очереди, этот объект уничтожается.

Даже если top() вернет вам ссылку на не const, эта ссылка станет болтаться, как только вы удалите элемент из очереди, а разыменование приведет к поведению undefined.

Это означает, что std::priority_queue возвращает вам ссылку на const, чтобы предотвратить беспорядок (намеренно или непреднамеренно) с его внутренним упорядочением - это почти та же причина, почему ключ ассоциативных контейнеров, таких как std::map и std::set const.

Вместо этого вы можете создать копию значения, возвращаемого top(), изменить эту копию, удалить оригинал и нажать копию в очередь:

SomeClass obj = pQueue.top();
pQueue.pop();
obj.setMember(42);
pQueue.push(std::move(obj)); // You can move obj into the queue if you no more need it

Если вам нужна эталонная семантика, с другой стороны, вам придется нажимать указатели в очередь (возможно, интеллектуальные указатели, в зависимости от вашего варианта использования) и предоставлять соответствующий настраиваемый критерий заказа, который будет заказывать эти указатели на основе свойств объектов, на которые они указывают.

В этом случае будьте осторожны, чтобы не изменять эти свойства во время выполнения таким образом, чтобы их порядок отличался. Это будет считаться "возиться с внутренним упорядочением контейнера" и приведет к поведению undefined.

Ответ 2

Я не думаю, что семантика значения играет здесь хоть какую-то роль. Все остальные контейнеры имеют одинаковую семантику значений, и почти все они предоставляют front() переменную ссылку.

Существует одна точная причина, по которой priority_queue запрещает модификацию элемента top(): это потому, что конкретный элемент находится сверху, потому что он был соответствующим образом квалифицирован так, согласно его текущему значению. Элементы в очереди приоритетов всегда сортируются в соответствии с критериями сравнения, настроенными для очереди (оператор по умолчанию). Изменяя элемент, вы можете потенциально уничтожить это условие и привести к тому, что все операции, которые используют отсортированное предварительное условие, вызывают поведение undefined.

Вкратце, причина, по которой priority_queue не позволяет изменять содержащиеся элементы, точно такая же, как в случае set и ключей для map.

Я понимаю, что у вас может быть определенный метод сравнения, вы используете поле, содержащее значение для сравнения, и вы собираетесь изменять любой контент объекта, кроме этого самого поля. Таким образом, вы не нарушаете требования упорядоченного порядка. Вы можете сделать две вещи:

  • Сделайте части вашего класса, которые следует изменить как mutable. Таким образом, вы можете получить элемент top() и изменить изменяемое содержимое, даже если это const.

  • Создайте свой собственный класс очереди приоритетов, получив std::priority_queue. Он имеет поле с именем c (в защищенном разделе, хотя), которое содержит ссылку на базовый контейнер. И базовый контейнер, скорее всего, имеет метод front(), который обращается к тому же элементу, что и top() в priority_queue. Однако для вашей собственной безопасности вы должны сделать свое ключевое поле const, чтобы оно было задано во время строительства и никогда не изменялось - чтобы свести к минимуму риск.

Ah, и эта проблема не может быть решена с помощью указателей - точечные объекты будут точно такой же константой. Эта "эталонная семантика" также не поможет вам, потому что если вы хотите иметь выделенный метод сравнения, вам придется заглянуть в содержимое объектов, и таким образом у вас будет такая же проблема. Это поможет вам, если бы вы полагались на простое сравнение значений указателей, но я бы скорее сомневался, что это может быть решением для 99% случаев.