Je trouve l'opération de mise à jour sur std::set
fastidieux car il n'y a pas une telle API sur cppreference . Donc, ce que je fais actuellement, c'est quelque chose comme ceci:
//find element in set by iterator
Element copy = *iterator;
... // update member value on copy, varies
Set.erase(iterator);
Set.insert(copy);
Fondamentalement, le retour de l'itérateur par Set
est un const_iterator
et vous ne pouvez pas modifier sa valeur directement.
Y a-t-il une meilleure manière de faire cela? Ou peut-être que je devrais remplacer std::set
en créant le mien (dont je ne sais pas exactement comment ça marche ..)
set
renvoie const_iterators
(la norme indique set<T>::iterator
est const
, et que set<T>::const_iterator
et set<T>::iterator
peut en fait être du même type - voir 23.2.4/6 dans n3000.pdf) car il s'agit d'un conteneur commandé. S'il renvoyait un iterator
normal, vous seriez autorisé à modifier la valeur des articles sous le conteneur, ce qui pourrait modifier la commande.
Votre solution est le moyen idiomatique de modifier des éléments dans un set
.
Il y a 2 façons de procéder, dans le cas simple:
mutable
sur la variable qui ne fait pas partie de la cléKey
Value
paire (et utiliser un std::map
)Maintenant, la question est pour le cas délicat: que se passe-t-il lorsque la mise à jour modifie réellement la partie key
de l'objet? Votre approche fonctionne, même si j'avoue que c'est fastidieux.
En C++ 17, vous pouvez faire mieux avec extract()
, grâce à P008 :
// 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));
Cela évitera une copie supplémentaire de votre type et une allocation/désallocation supplémentaire, et fonctionnera également avec les types à déplacement uniquement.
Vous pouvez également extract
par clé. Si la clé est absente, cela renverra un nœud vide:
auto node = Set.extract(key);
if (node) // alternatively, !node.empty()
{
node.value() = 42;
Set.insert(std::move(node));
}
Mise à jour: Bien que ce qui suit soit vrai à partir de maintenant, le comportement est considéré comme défaut et sera modifié dans la prochaine version de la norme. C'est très triste.
Il y a plusieurs points qui rendent votre question assez confuse.
std::set
Est une classe et ne peut donc rien renvoyer.s.erase(iter)
, alors iter
n'est pas un const_iterator
. erase
nécessite un itérateur non const.std::set
Qui renvoient un itérateur renvoient un itérateur non const tant que l'ensemble est également non const.Vous êtes autorisé à modifier la valeur d'un élément d'un ensemble tant que la mise à jour ne modifie pas l'ordre des éléments. Le code suivant compile et fonctionne très bien.
#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;
}
Si votre mise à jour modifie l'ordre des éléments, vous devez alors effacer et réinsérer.
Vous pouvez utiliser un std::map
au lieu. Utilisez la partie de Element
qui affecte l'ordre de la clé et mettez tout Element
comme valeur. Il y aura quelques duplications mineures de données, mais vous aurez des mises à jour plus faciles (et peut-être plus rapides).
J'ai rencontré le même problème en C++ 11, où en effet ::std::set<T>::iterator
est constant et ne permet donc pas de changer son contenu, même si l'on sait que la transformation n'affectera pas le <
invariant. Vous pouvez contourner ce problème en enveloppant ::std::set
dans une mutable_set
tapez ou écrivez un wrapper pour le contenu:
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;
}
};
Je trouve cela beaucoup plus simple et cela fonctionne dans 90% des cas sans que l'utilisateur ne remarque qu'il y ait quelque chose entre l'ensemble et le type réel.
C'est plus rapide dans certains cas:
std::pair<std::set<int>::iterator, bool> result = Set.insert(value);
if (!result.second) {
Set.erase(result.first);
Set.insert(value);
}
Si la valeur n'est généralement pas déjà dans le std::set
alors cela peut avoir de meilleures performances.