2

I need to modify the key of an element inside a std::map.
Currently, I'm doing this by erasing the element and re-inserting it with a different key.
Unfortunately, this is slow -- it involves an extra heap deallocation and reallocation.

What's the best way -- if there is any way -- to avoid the automatic node reallocation and destruction when I need to modify an element's key?

user541686
  • 205,094
  • 128
  • 528
  • 886
  • @MattMcNabb: Nope, while the title is similar, the contents of the question don't seem to be. I'm not trying to re-index *all* the elements, but only a single one. – user541686 Jul 18 '14 at 05:03
  • If heap allocation has too much overhead for you, a custom allocator is the way to go. – Brian Bi Jul 18 '14 at 05:04
  • @JerryCoffin: Yeah I was thinking of that too, I was just hoping for a better way that doesn't depend on me changing the allocator. :\ But if you don't know of a better way feel free to just post that as an answer. – user541686 Jul 18 '14 at 05:04
  • It may be worth having a look at [boost flat associative containers](http://www.boost.org/doc/libs/1_55_0/doc/html/container/non_standard_containers.html#container.non_standard_containers.flat_xxx). – juanchopanza Jul 18 '14 at 05:56
  • 1
    @juanchopanza: Haha thanks but I'm already aware of those, they're most *definitely* not appropriate for my use case unfortunately! I really need all (or almost all) the guarantees provided by the trees -- the ordering, the iterator integrity, the logarithmic complexity, etc. – user541686 Jul 18 '14 at 06:09
  • note the answer by Howard Hinnant in the linked duplicate: he [proposed](http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-closed.html#839) avoiding the reallocation by adding `splice()` functionality. – TemplateRex Jul 21 '14 at 08:34

3 Answers3

5

I don't think there is a way to avoid the node being deallocated and reallocated.

About the best I can think of would be to specify an Allocator with a lookaside list so when a node is reallocated soon after being freed, you can provide the same node very quickly/easily.

If you're certain the key change won't affect the relative order of the keys, you could modify the key in place (e.g., by specifying it as a mutable member), but doing so would lead to undefined behavior--chances are pretty good you can get away with it, but it's still officially undefined behavior.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • 1
    In the special case where I know the element's *order* won't change relative to the other elements, is there anything I could do? (I'm thinking along the lines of making the key `mutable`, but I'm not sure what obstacles might lay ahead there.) – user541686 Jul 18 '14 at 05:07
  • +1 best answer so far. – user541686 Jul 18 '14 at 06:12
0

You can at least lesson the constructing part, depending on the insides of you mapped class. Consider the following example:

class A
{
    public:
    A(){};
    A(int a)
    : a_(a)
    {
        std::cout << "construct" << std::endl;
    }

    A& operator=(const A& b)
    {
       a_ = b.a_;
       std::cout << "=" << std::endl;
    }

    A(A&& o)
    : a_(o.a_)
    {
        std::cout << "moved" << std::endl;
    }

private:
    int a_;
};

It has a move constructor. By using map.emplace you can skip the reconstruction of your classes members:

std::map<int, A> mapper;

mapper[1] = A(1);
mapper[2] = A(2);
mapper.emplace(3, std::move(mapper[2]));
mapper.erase(mapper.find(2));

This will print out the following:

construct
=
construct
=
moved

indicating that the first two are constructed but the second one is moved. You will still have to delete your element and be careful about the move semantics.

If your int a_ was replaced with something massive, you would be reducing the deep copy time and the deletion time by moving the data and deleting nothing.

Fantastic Mr Fox
  • 32,495
  • 27
  • 95
  • 175
  • I think you missed the point of the question. The key and value are cheap to copy (in fact, they can be plain integers); that's not the problem. The problem is keeping the *node* inside of the map or set intact instead of destroying it only to recreate it moments later. – user541686 Jul 18 '14 at 06:13
  • @Mehrdad yeah but it was fun for me to investigate this anyway. – Fantastic Mr Fox Jul 18 '14 at 06:15
0

Perhaps this changes your design a bit, but consider instead using a pointer (or better, a shared_ptr) as the key. That way you can update the underlying data without ever updating the key.

tinkertime
  • 2,972
  • 4
  • 30
  • 45
  • 1
    It's not a question of being "able" to in the sense of getting around the compiler, but it's a question of being "able" to in the sense that it violates the invariants of the data structure. This doesn't really fix any problem. – user541686 Jul 18 '14 at 06:11
  • @Mehrdad i think thats a fair point. Perhaps reading your question a bit too literally at 2am. – tinkertime Jul 18 '14 at 06:21