Search code examples
c++setmultiset

Update element in a set of pointer of elements before deletion


I have some instances of a class allocated in an unordered_map. I also have different containers that store pointers to these elements with different orderings. Which means that for example, I have a std::set of pointers to the elements allocated in the map, ordered by a subset of the instances' fields.

Since I have access to the real elements, I can change their fields, but I know that I shouldn't do that on the fields that are used in the ordering of the set. In fact, what I need to do before changing those fields is to delete the object's pointer from the set, change those fields and insert it back again, like this:

set<Element*, Comparator> s; // Elements ordered by field_2

s.erase(element);
element->field_2 = 4;
s.insert(element);

However, the other containers that maintain different orderings are implemented by myself, and I know that I can change those values and then notify the container that the fields have been updated. So I'm wondering if I can change the order of those instructions to this one:

element->field_2 = 4;
s.erase(element);
s.insert(element);

The reason I want to do it this way is that I want all those containers to share the same interface. So ideally I would like to change the fields and then call the container's method container.value_updated(element).

So, can I modify the critical field_2 and then immediately call delete & insert? Or the deletion will fail since the value of field_2 can be inconsistent? (I think this will be implementation dependent but I want to make sure)


Solution

  • Data which is used as key in a std::set may not be changed. Otherwise, the order of std::set is broken and addressing elements in the std::set cannot work anymore.

    Out of curiosity, I tried to do it the wrong way.

    Although I know it is undefined behavior, I got this running – a nice demonstration that bad things happen:

    #include <iostream>
    #include <set>
    #include <vector>
    
    typedef int Entry;
    
    struct Less {
      bool operator()(const Entry *p1, const Entry *p2) const
      {
        return *p1 < *p2;
      }
    };
    
    int main()
    {
      const int N = 10;
      std::vector<int> data;
      for (int i = 0; i < N; ++i) data.push_back(i);
      std::set<Entry*, Less> set;
      for (int &value : data) set.insert(&value);
      // do it wrong
      data[2] = 12;
      set.erase(&data[2]);
      set.insert(&data[2]);
      // check result
      for (const Entry *pValue : set) std::cout << ' ' << *pValue;
      std::cout << '\n';
      // done
      return 0;
    }
    

    Output:

     0 1 12 3 4 5 6 7 8 9 12
    

    Live Demo on coliru