Ich finde den Update-Vorgang std::set
mühsam, da es bei cppreference keine solche API gibt . Was ich derzeit mache, ist ungefähr so:
//find element in set by iterator
Element copy = *iterator;
... // update member value on copy, varies
Set.erase(iterator);
Set.insert(copy);
Grundsätzlich ist der Iterator return by Set
a const_iterator
und Sie können seinen Wert nicht direkt ändern.
Gibt es einen besseren Weg, dies zu tun? Oder vielleicht sollte ich es überschreiben, std::set
indem ich mein eigenes erstelle (was ich nicht genau weiß, wie es funktioniert ..)
Antworten:
set
Rückkehrconst_iterators
(der Standard sagtset<T>::iterator
istconst
, und dassset<T>::const_iterator
undset<T>::iterator
in der Tat kann die gleiche Art sein - siehe 23.2.4 / 6 in n3000.pdf) , weil es ein geordnete Container ist. Wenn ein reguläreriterator
Wert zurückgegeben wird, können Sie den Artikelwert unter dem Container ändern und möglicherweise die Reihenfolge ändern.Ihre Lösung ist die idiomatische Möglichkeit, Elemente in a zu ändern
set
.quelle
std::set
kehren nicht zurückconst_iterator
und Sie können die Elemente ändern, wenn Sie vorsichtig sind. Warum bekommt diese (falsche) Antwort so viele positive Stimmen? Was vermisse ich?Im einfachen Fall gibt es zwei Möglichkeiten:
mutable
für die Variable verwenden, die nicht Teil des Schlüssels sindKey
Value
Paar aufteilen (und a verwendenstd::map
)Die Frage ist nun für den schwierigen Fall: Was passiert, wenn das Update tatsächlich den
key
Teil des Objekts ändert ? Ihr Ansatz funktioniert, obwohl ich zugebe, dass es langweilig ist.quelle
In C ++ 17 können Sie
extract()
dank P0083 besser damit umgehen :// 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));
Dies vermeidet eine zusätzliche Kopie Ihres Typs und eine zusätzliche Zuordnung / Freigabe und funktioniert auch mit Nur-Verschieben-Typen.
Sie können auch
extract
per Schlüssel. Wenn der Schlüssel fehlt, wird ein leerer Knoten zurückgegeben:auto node = Set.extract(key); if (node) // alternatively, !node.empty() { node.value() = 42; Set.insert(std::move(node)); }
quelle
Update: Obwohl das Folgende ab sofort zutrifft, wird das Verhalten als Fehler angesehen und in der kommenden Version des Standards geändert. Wie sehr traurig.
Es gibt mehrere Punkte, die Ihre Frage ziemlich verwirrend machen.
std::set
ist eine Klasse und kann daher nichts zurückgeben.s.erase(iter)
, danniter
ist keinconst_iterator
.erase
erfordert einen nicht konstanten Iterator.std::set
davon geben einen Iterator zurück, der einen nicht konstanten Iterator zurückgibt, solange die Menge ebenfalls nicht konstant ist.Sie können den Wert eines Elements einer Menge ändern, solange die Aktualisierung die Reihenfolge der Elemente nicht ändert. Der folgende Code wird kompiliert und funktioniert einwandfrei.
#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; }
Wenn Ihr Update die Reihenfolge der Elemente ändert, müssen Sie es löschen und erneut einfügen.
quelle
Möglicherweise möchten Sie
std::map
stattdessen eine verwenden. Verwenden Sie den Teil, derElement
sich auf die Bestellung des Schlüssels auswirkt, und geben Sie allesElement
als Wert ein. Es wird einige kleinere Daten duplizieren, aber Sie werden einfachere (und möglicherweise schnellere) Updates haben.quelle
Ich bin auf dasselbe Problem in C ++ 11 gestoßen, wo es tatsächlich
::std::set<T>::iterator
konstant ist und daher keine Änderung des Inhalts zulässt, selbst wenn wir wissen, dass die Transformation die<
Invariante nicht beeinflusst . Sie können dies umgehen, indem Sie::std::set
einenmutable_set
Typ einschließen oder einen Wrapper für den Inhalt schreiben: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; } };
Ich finde das viel einfacher und es funktioniert in 90% der Fälle, ohne dass der Benutzer bemerkt, dass zwischen dem Set und dem tatsächlichen Typ etwas liegt.
quelle
Dies ist in einigen Fällen schneller:
std::pair<std::set<int>::iterator, bool> result = Set.insert(value); if (!result.second) { Set.erase(result.first); Set.insert(value); }
Wenn der Wert normalerweise noch nicht im Wert ist,
std::set
kann dies eine bessere Leistung haben.quelle