Question

I was curios if default destructor is called, when I'm removing an element from an std::map. Here is an example which I have made:

class CTestMap{
public:
    CTestMap() {
        std::cout << "default constructor called" << std::endl;
    }
    CTestMap(int id) {
        std::cout << "created object: " << id << std::endl;
        m_id = id;

    }
    ~CTestMap() {
            std::cout << "destroyed object: " << this->m_id << std::endl;
    }
    int get_id(){
        return m_id;
    }
    int m_id;
};

int main(void){

    std::map<int, CTestMap>m;
    std::map<int, CTestMap>::iterator m_it;

    std::cout << "created map " << std::endl;

    CTestMap t1(1);
    std::cout << "created test object: " << t1.get_id() << std::endl;
    CTestMap t2(2);
    std::cout << "created test object: " << t2.get_id() << std::endl;
    CTestMap t3(3);
    std::cout << "created test object: " << t3.get_id() << std::endl;

    m[1] = t1;
    m_it = m.find(1);
    std::cout << "inserted test object: " <<  m_it->second.get_id() << std::endl;

    m[2] = t2;
    m_it = m.find(2);
    std::cout << "inserted test object: " <<  m_it->second.get_id() << std::endl;

    m[3] = t3;
    m_it = m.find(3);
    std::cout << "inserted test object: " <<  m_it->second.get_id() << std::endl;

    m_it = m.find(1);
    std::cout << "will now erased test object: " << m_it->second.get_id() << std::endl;
    m.erase(m.find(1));
    std::cout << "erased test object: " << m[1].get_id() << std::endl;

    m_it = m.find(1);
    std::cout << "object shall no longer exist: " << m_it->second.get_id() << std::endl;


    while(1);
return 0;
}

An here is the output:

./htest
created map
created object: 1
created test object: 1
created object: 2
created test object: 2
created object: 3
created test object: 3
default constructor called
destroyed object: 9377935
destroyed object: 9377935
inserted test object: 1
default constructor called
destroyed object: 9377935
destroyed object: 9377935
inserted test object: 2
default constructor called
destroyed object: 9377935
destroyed object: 9377935
inserted test object: 3
will now erased test object: 1
destroyed object: 1
default constructor called
destroyed object: 158830600
destroyed object: 158830600
erased test object: 158830600
object shall no longer exist: 158830600

Questions are:

  1. Why so many times default constructor is called, when I'm only creating 3 objects using my own constructor ?
  2. Can I, based on this example say, that each time I'm erasing any object from the std::map, its destructor is called ? Is this general behavior of a std::map ? I could not find this information.
  3. What if I'm storing pointers to objects (I'm creating them using 'new' operator) ? When then delete shall be called ?
Was it helpful?

Solution

std::map stores a copy of the object you insert. When the object is removed, it is this copy which is destructed. So after m[1] = t1;, there are two identical instances of CTestMap: t1 and the one in the map.

Also: m[1] = t1; will first create a new entry in the map, using the default constructor, and later assign t1 to it.

In general, if you want to trace instance lifetime like this, you need provide a user defined copy constructor and assignment operator which trace as well. And you probably want to output the this pointer in all of the traces. (Another technique would be to dote each object with an immutable unique identifier:

#define TRACE(m) std::cout << #m << '(' << m_objectId << ')' << std::endl
static int currentObjectId = 0;

class TestMap
{
    int m_id;
    int const m_objectId;
public:
    TestMap()
        : m_id( 0 )
        , m_objectId( ++ currentObjectId )
    {
        TRACE(DFLT);
    }
    TestMap( int id )
        : m_id( id )
        , m_objectId( ++ currentObjectId )
    {
        TRACE(CTOR);
    }
    TestMap( TestMap const& other )
        : m_id( other.m_id )
        , m_objectId( ++ currentObjectId )
    {
        TRACE(COPY);
    }
    ~TestMap()
    {
        TRACE(DTOR);
    }
    TestMap& operator=( TestMap const& other )
    {
        m_id = other.m_id;
        TRACE(ASGN);
        return *this;
    }
};

You might want to add additional information (like m_id) to the trace as well.

Also: your last output invokes undefined behavior. After m.find(i), you should check first that the iterator hasn't returned m.end(). If it has, dereferencing isn't allowed. So your test output should be something like:

void
testOutput( std::map<int, TestMap> const& m, int i )
{
    std::map<int, TestMap>::const_iterator entry = m.find( i );
    if ( entry == m.end() ) {
        std::cout << "no object at " << i << std::endl;
    } else {
        std::out << "object " << entry->second.m_id << " at " << i << std::endl;
    }
}

(Finally: I think Microsoft has preempted the C prefix for classes, so you should avoid it. If you want a prefix, choose something else, to avoid confusion.)

OTHER TIPS

If you store an actual object (rather than a reference or pointer), yes, the object gets destroyed when you erase it.

If you store pointers or references, then the object is not destroyed, and delete is not called on a pointer. If you want that to happen automatically, you should use a smart pointer (e.g. unique_ptr or shared_ptr depending on what behaviour you want).

If you don't use smart pointers, then you will need to take pointer stored, and delete the object yourself (after using erase to remove the element from the map).

Your default constructor is called a fourth time, because m[1] in

std::cout << "erased test object: " << m[1].get_id() << std::endl;

will construct a new object with key "1". This is because such an element doesn't exist in the map yet – otherwise it would just return that already existing object. (It did exist before, but you erased it in the line above! ;])

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top