tags:

views:

124

answers:

5

I have found a very prejudicial fact about stl maps. For some reason I cant get objects being inserted in the map to get constructed/destructed only once.

Example:

struct MyObject{
    MyObject(){
        cout << "constructor" << endl;
    }
    ~MyObject(){
        cout << "destructor" << endl;
    }
};
int main() {
    std::map<int, MyObject> myObjectsMap;
    myObjectsMap[0] = MyObject();
    return 0;
}

returns:

constructor
destructor
destructor
constructor
destructor

If I do:

typedef std::pair<int, MyObject> MyObjectPair;
myObjectsMap.insert( MyObjectPair(0,MyObject()));

returns:

constructor
destructor
destructor
destructor

I'm inserting Objects responsible for their own memory allocation, so when destructed they'll clean themselves up, being destructed several times is causing me some trouble.

+1  A: 

That's the way map and the other containers work, you can't get around it. That's why std::auto_ptr can't be used in a collection, for example.

Mark Ransom
I think I'll switch to a vector and hold the pointers in the map.
Alberto Toglia
+5  A: 

I suggest you add a copy constructor - that's what is being used for the 'missing' constructions I think.

Code:

#include <iostream>
#include <map>

using namespace std;

struct MyObject{
    MyObject(){
        cout << "no-arg constructor" << endl;
    }
    MyObject(const MyObject&) {
    cout << "const copy constructor" << endl;
    }
    ~MyObject(){
        cout << "destructor" << endl;
    }
};
int main() {
    std::map<int, MyObject> myObjectsMap;
    myObjectsMap[0] = MyObject();
    return 0;
}

Output:

no-arg constructor
const copy constructor
const copy constructor
destructor
destructor
no-arg constructor
destructor
destructor
Douglas Leeder
+2  A: 

std::map is allowed to make as many copies of your objects as it wishes. This is implementation defined and you have no control over this. The "missing" constructions you notice, by the way, may be for calling the copy-constructor, which you didn't define.

What you can do, however is use a flyweight so constructing an object in fact fetches a existing object from a pool of pre-existing objects, and destructing an object does nothing. The pool never frees its objects, but it always maintains a handle over all of them. This way, your memory usage is large from start to stop, but it doesn't change much throughout the life of the program.

wilhelmtell
+2  A: 

To be able to be used in a standard container your objects must be copyable and assignable. If your objects don't conform to this you may are likely to have problems.

That said, if (as your sample code indicates) you just need a default constructed object inserted in the map you can just use operator[] for its side effect:

// Insert default constructed MyObject at key 0
myObjectsMap[0];

Edit

I'm not quite clear from your question, but if you're unclear about the number of objects constructed and believe there is a constructor/destructor mismatch then note that the compiler will provide a copy constructor which doesn't log to std::cout as you don't provide a user-declared one.

Charles Bailey
Alberto Toglia
Have you tested what `myObjectsMap[0];` actually does?
Charles Bailey
Well, not really, I was actually using the insert method, it has less overhead but still produces more copies than I was expecting it to. Either way I think I'll stay with the vector and use the map only for pointers. The post above was quite clear of whats actually going on. Good to know.
Alberto Toglia
+1  A: 

When you say myObjectsMap[0], you are calling the default constructor for MyObject. This is because there is nothing in [0] yet, and you just accessed it. It's in the manual.

When you hit MyObject(); you are creating a temporary MyObject instance using the default constructor.

Because you allowed the compiler to define your copy constructor, you see more destructor than constructor messages. (There can only be one destructor, but many constructors, unlike building a house.) If you object should never be copied this way, then you likely want to declare a private copy constructor and copy assignment operator.

You're calling the default constructor and the copy constructor twice each with this code:

myObjectsMap[0] = MyObject();

When you do this:

myObjectsMap.insert( MyObjectPair(0,MyObject()));

you call the default constructor once and the copy constructor 3 times.

You should likely use pointers as map values instead of the objects themselves, in particular I suggest looking at shared_ptr.

note: tests were done using GCC 3.4.5 on a Windows NT 5.1 machine.
TerryP