Game Development Community

STL map question

by Mike Stoddart · in Technical Issues · 10/05/2002 (5:47 pm) · 2 replies

I'm trying to use an STL map to store instances of classes. I have:

Quote:typedef map group;
group items;

I'm creating instances of myClass to add into the map:

Quote:myClass *item = new myClass();
items[1] = *item;

I'm not hard coding the value 1 into the code; it's only there to simplify the code I'm showing.

I have also added a deep copy constructor to the class, but when I add the item into the map, the copy constructor and the destructor are being called twice.

I can't work out why this is happening; is this normal when adding a class into a map?

Thanks

#1
10/10/2002 (10:01 am)
typedef map group;
group items;
myClass *item = new myClass();
items[1] = *item;
-----------

Ok... it's happening twice because of the whole part on the left side that reads "items[1]". I'll step through the source for map to go over why it's twice, but this is why I do not like mapping values to class objects (I map to pointers, instead). I'll reference the source that's in the VC++ compiler.

To begin with, let's say your myClass has an int i you initialize to 3 in the default constructor and increment in the copy constructor. Then let's say you made item->i equal to 7 so you can distinguish it from all the other objects being made in between.

Ok, so the items[1]: first you're calling the [] operator whose code reads as follows:
_Tref operator[](const key_type& _Kv)
{iterator _P = insert(value_type(_Kv, _Ty())).first;
return ((*_P).second); }

Essentially it's inserting a valid value for the key first and then it'll return so you can run operator= on it to complete the right side of your original call. It sees it needs to call value_type(_Kv, _Ty()) BUT you see the _Ty() part? So it first needs to call your default constructor on your class. So it does that. Now it has a new myClass object whose i value is 3. Then it needs to call the constructor for the value_type pair for it. And that code is:

pair(const _T1& _V1, const _T2& _V2)
: first(_V1), second(_V2) {}

You see the second(_V2) part? This is where it needs to call your copy constructor the first time. So it calls the copy constructor on that new object you made from the _Ty() call so now this new object's i is incremented up to 4. So at this point you have item with i set to 7, an object made from the default constructor with i set to 3, and then a copy of that one with i incremented up to 4. Now that's happy and insert has to be called to prep the location in the map for the new value you are going to be giving it later.

The code for the insert call there is in the xtree include file, by the way. It then goes (most likely) goes through this piece of code:

return (_Pairib(_Insert(_X, _Y, _V), true));

The third line of THAT _Insert call is

_Consval(&_Value(_Z), _V);

That will call your copy constructor again. It calls it on the copied object that had i set to 4. So this (yet another) NEW object is copied from the 4 object and has its i incremented up to 5. Now everything starts returning back to its caller. Once you get back to where that insert call in operator[] returns, the other objects are no longer being referenced and are going out of scope, so it destructs the copied object with the value of 4. Then it has to destroy the other object it made with the value of 3. It doesn't destroy the object with the 5 because it's being referenced by the iterator that's being returned. So NOW you're left with items[1] which is finally some object with the i set to 5 and the rest of your original code which was "= *item;" Since you are assigning it to an object and not its pointer, it is going to call your operator= method. And in there hopefully you told it to copy over all the appropriate values from *item to the new object (that has i set to 5). And there you have it. Your default constructor was called once, your copy constructor got called twice, your destructor was called twice, and then the operator= which you wanted to be called in the first placed was called.
#2
10/15/2002 (9:19 am)
this is happening as map and other stl containers store the stuff by value... you are better off storing the pointer (remember to delete the pointers at the time of clearing). So derive a call from map and override the clear to iterate and delete all the pointers.

also you can write an auto_pointer wrapper for you class

class FooWrapper
{
public:
FooWrapper(Foo * f)
{
this->_f = f;
}
~FooWrapper()
{
if(_f) delete f;
}
FooWrapper(const FooWrapper& other)
{
_f = other._f;
}
Foo* operator ->()
{
return _f;
}
operator Foo*()
{
return _f;
}
private:
Foo *_f
}

now declare the map as map and add the pointer as FooWrapper(pFoo)