Reputation: 3079
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:
std::map
, its destructor is called ? Is this general behavior of a
std::map
? I could not find this information.delete
shall be called ?Upvotes: 3
Views: 8464
Reputation: 153919
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.)
Upvotes: 8
Reputation: 126
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! ;])
Upvotes: 0
Reputation: 129374
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
).
Upvotes: 4