3

I have been using unordered_map<int, myObject> with myObject* pointers to the objects in the unordered map. This has worked for a while, but I recently dicovered that I wrongly assumed that the memory location of myObject added to the unordered map would always remain the same.

I can solve the problem by using unordered_map<int, myObject*> and new and delete when adding and removing elements from the unordered map.

Since I have quite some code I would not want to add new and delete every place in the code where I am modifying the unordered map, I would rather try to overload the unordered_map::operator[] and unordered_map::erase() such that the usage of new and delete would happen transparently and I would not have to change my existing code. unordered_map::operator[] could then return a reference to the myObject itself rather than the pointer.

I have tried to inherit unordered_map but I am not sure how I should add the template argument list:

using namespace std;

template<class _Kty,
class _Ty,
class _Hasher = hash<_Kty>,
class _Keyeq = equal_to<_Kty>,
class _Alloc = allocator<pair<const _Kty, _Ty> > >
class  my_unordered_map : public unordered_map<_Umap_traits<_Kty, _Ty,
_Uhash_compare<_Kty, _Hasher, _Keyeq>, _Alloc, false> >
{

};

But I am getting errors such as:

error C2976: 'std::unordered_map' : too few template arguments
error C2955: 'std::unordered_map' : use of class template requires template argument list

Then I realized it might be possible to add a specialization to std when using the myObject* type with unordered_map, but I am not sure if it is even possible to overload the operator[] with a specialization.

I apprechiate any help I could get, thank you!

Edit:

I have now created a template <class mapped_type> class with an unordered_map<int, mapped_type*> as an internal structure. The operator[] was fairly straightforward to include:

template <class mapped_type> class MyMap {
public:
    std::unordered_map<int, mapped_type*> internal_map;

    mapped_type& operator[](int&& _Keyval)
    {   // find element matching _Keyval or insert with default mapped
        mapped_type*& ptr = internal_map[_Keyval];
        if (ptr == nullptr) ptr = new mapped_type();
        return *ptr;
    }
}

void erase(const int& _Keyval)
{   // erase and count all that match _Keyval
    mapped_type* ptr = internal_map[_Keyval];
    if (ptr) delete ptr;
    internal_map.erase(_Keyval);
}

void clear()
{   // erase all
    internal_map.clear();
}

Now the problem is the erase methods (default methods are included in std::_Hash). I do not really need an iterator so I guess the best way might be to use the operator[] method first to find the entry and then use delete before removing it from the internal_map, or do you have any other ideas that might be more suitable?

Edit: Added suggestion for erase. This makes sense right?

Ronny
  • 454
  • 4
  • 15
  • 4
    What about using a value-semantics wrapper of `myObject`? Something similar to `unique_ptr` but with automatic creation of a `myObject` in the default ctor. Or you could use a `std::map`, its `insert` function does not invalidate references. – dyp Jan 11 '14 at 17:03
  • Thank you for the comment, I was not aware that the `std::map` did not invalidate the references. I have updated my question now and I will first try to solve it in the suggested manner. – Ronny Jan 11 '14 at 19:19
  • 1
    You're changing a *copy* of the pointer stored in the map. You'd have to use something like `mapped_type*& ptr = internal_map[_Keyval];` – dyp Jan 11 '14 at 19:38
  • I am now using MyMap with two different classes, A and B. However, only when I use _MyMapA[integer]_ I get this error: `error C2679: binary '[' : no operator found which takes a right-hand operand of type 'int' (or there is no acceptable conversion)` and not on _MyMapB[integer]_. I don't see why this would make a difference whatsoever to the `Operator[]` method? – Ronny Jan 11 '14 at 20:07
  • And what is strange is that when I substitute the int variable, i.e.: _MyMapA[integer]_ with a number _MyMapA[123]_ I do not get any errors. – Ronny Jan 11 '14 at 20:17
  • Oh, I've missed that. `mapped_type& operator[](int&& _Keyval)` is taking an *rvalue* reference, so you can't bind it to an lvalue `int`. In this case, if you *really* don't want to use a (unordered) map of a wrapper, then you could use use `mapped_type& operator[](int _Keyval)` – dyp Jan 11 '14 at 20:20
  • 5
    "I recently dicovered that I wrongly assumed that the memory location of myObject added to the unordered map would always remain the same." You were correct, and are now mistaken: operations that modify an `unordered_map` can invalidate *iterators*, but not pointers or references to elements (with the obvious exception of erased elements). The elements themselves will never move. – Casey Jan 11 '14 at 20:28
  • @dyp Thank you very much again! I could probably use a wrapper, but I'm not so experienced in c++ and since I've used the whole day to get this to work I think I'll use it like this for now. – Ronny Jan 11 '14 at 20:34
  • @Casey Thank you very much for your input! I did a check where they were the pointers were suddenly pointing to other items in the list. However, I recreated a similar test now and it seems to be working fine. It might have something to do with using two threads, I will investigate it further. – Ronny Jan 11 '14 at 20:52

1 Answers1

0

To inherit from std::unordered_map it is sufficient to use

template <class T,class V> 
class MyMap : public unordered_map<T, V>

if its ok for you to use the std allocator and hash function. But beware, that there isn't a virtual destructor in the standard containers.

Anyway, what you want to do in the end sounds to me like you want to have an intrusive container. If so, then there is this related SO question.

Community
  • 1
  • 1
Sebastian
  • 8,046
  • 2
  • 34
  • 58