Обновление С++ std :: set утомительно: я не могу изменить элемент на месте
Я считаю, что операция обновления на std::set
утомительна, так как на cppreference такого API нет. Так что я сейчас делаю что-то вроде этого:
//find element in set by iterator
Element copy = *iterator;
... // update member value on copy, varies
Set.erase(iterator);
Set.insert(copy);
По сути, возвращаемое Set
значение итератора является const_iterator
и вы не можете напрямую изменить его значение.
Есть лучший способ сделать это? Или, может быть, я должен переопределить std::set
, создав свой собственный (который я не знаю точно, как это работает..)
Ответы
Ответ 1
set
возвращает const_iterators
(стандарт говорит, что set<T>::iterator
- const
, и что set<T>::const_iterator
и set<T>::iterator
могут быть одного и того же типа - см. 23.2.4/6 в n3000.pdf) потому что это упорядоченный контейнер. Если он вернул обычный iterator
, вам было бы позволено изменить значение элементов из контейнера, что может изменить порядок.
Ваше решение - это идиоматический способ изменения элементов в set
.
Ответ 2
Есть два способа сделать это, в удобном случае:
- Вы можете использовать
mutable
для переменной, которая не является частью ключа
- Вы можете разделить свой класс на пару
Key
Value
(и использовать std::map
)
Теперь вопрос заключается в сложном случае: что происходит, когда обновление фактически изменяет часть объекта Key
объекта? Ваш подход работает, хотя я признаю, что это утомительно.
Ответ 3
Обновление: Хотя на данный момент верно следующее: поведение считается defect и будет будут изменены в следующей версии стандарта. Как очень грустно.
Есть несколько моментов, которые делают ваш вопрос довольно запутанным.
- Функции могут возвращать значения, классы не могут.
std::set
является классом и, следовательно, ничего не может вернуть.
- Если вы можете позвонить
s.erase(iter)
, то iter
не является const_iterator
. erase
требуется инертный нетератор.
- Все функции-члены
std::set
, которые возвращают итератор, возвращают не-const-итератор, если множество не является константным.
Вам разрешено изменять значение элемента набора, если обновление не изменяет порядок элементов. Следующий код компилируется и работает отлично.
#include <set>
int main()
{
std::set<int> s;
s.insert(10);
s.insert(20);
std::set<int>::iterator iter = s.find(20);
// OK
*iter = 30;
// error, the following changes the order of elements
// *iter = 0;
}
Если ваше обновление изменяет порядок элементов, вам необходимо удалить и повторно вставить.
Ответ 4
Вы можете вместо этого использовать std::map
. Используйте часть Element
которая влияет на порядок ключей, и поместите весь Element
в качестве значения. Будет небольшое дублирование данных, но у вас будут более простые (и, возможно, более быстрые) обновления.
Ответ 5
В С++ 17 вы можете сделать лучше с extract()
, благодаря P0083:
// remove element from the set, but without needing
// to copy it or deallocate it
auto node = Set.extract(iterator);
// make changes to the value in place
node.value() = 42;
// reinsert it into the set, but again without needing
// to copy or allocate
Set.insert(std::move(node));
Это позволит избежать дополнительной копии вашего типа и дополнительного распределения/освобождения, а также будет работать с типами только перемещения.
Вы также можете extract
ключ. Если ключ отсутствует, это возвращает пустой узел:
auto node = Set.extract(key);
if (node) // alternatively, !node.empty()
{
node.value() = 42;
Set.insert(std::move(node));
}
Ответ 6
Я столкнулся с той же проблемой в С++ 11, где действительно ::std::set<T>::iterator
является константой и, следовательно, не позволяет изменять его содержимое, даже если мы знаем, что преобразование не повлияет на инвариант <
. Вы можете обойти это, обернув ::std::set
в тип mutable_set
или напишите обертку для содержимого:
template <typename T>
struct MutableWrapper {
mutable T data;
MutableWrapper(T const& data) : data(data) {}
MutableWrapper(T&& data) : data(data) {}
MutableWrapper const& operator=(T const& data) { this->data = data; }
operator T&() const { return data; }
T* operator->() const { return &data; }
friend bool operator<(MutableWrapper const& a, MutableWrapper const& b) {
return a.data < b.data;
}
friend bool operator==(MutableWrapper const& a, MutableWrapper const& b) {
return a.data == b.data;
}
friend bool operator!=(MutableWrapper const& a, MutableWrapper const& b) {
return a.data != b.data;
}
};
Я нахожу это намного проще, и он работает в 90% случаев без того, чтобы пользователь даже замечал, что есть что-то между набором и фактическим типом.
Ответ 7
Если ваш набор содержит объекты, которые вы хотите изменить, убедитесь, что вы сохранили их указатели в наборе.
Ну, это не мешает вам вставлять несколько объектов с одинаковым значением (ну, вы не можете вставлять один и тот же объект несколько раз), но это полезно, если вы хотите иметь контейнер с быстрой вставкой и удалением.
Ответ 8
Это быстрее в некоторых случаях:
std::pair<std::set<int>::iterator, bool> result = Set.insert(value);
if (!result.second) {
Set.erase(result.first);
Set.insert(value);
}
Если значение обычно отсутствует в std::set
это может повысить производительность.