map of object pointers

Hello

Why does this code call the conctructor and destructor twice? im know stl::map creates a copy but dont fully understand why both are called.

Does anyone know of a good beginner friendly article on using stl::map with class objects an pointers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>
#include <limits>
#include <map>
using std::cout;
using std::endl;
using std::map;

class Room_Data
{
      public:
             Room_Data() {cout<<"Construct"<<endl;}
             ~Room_Data() {cout<<"Destruct"<<endl;}
             char*        name;
             int          vnum;
};

int main()
{
    Room_Data *pRoom;
    pRoom = new Room_Data;
    pRoom->vnum = 2400;
    
    map<int, Room_Data> rMap;
    map<int, Room_Data>::reverse_iterator iter;
    
    rMap[pRoom->vnum] = *pRoom;
    
    for ( iter = rMap.rbegin() ; iter != rMap.rend(); iter++ )
    {
        cout<<iter->second.vnum<<endl;
        }
    cout<<pRoom->vnum<<endl;
    cout << "Press ENTER to quit." << std::flush;
    std::cin.ignore( std::numeric_limits <std::streamsize> ::max(), '\n' );
    return 0;
}
Ctor call #1:
pRoom = new Room_Data; - This calls the constructor to create a new Room_Data

Ctor call #2:
rMap[pRoom->vnum] - This calls the constructor to create a new "blank" Room_Data to put at key=2400. Remember that the [] operator will create an entry if there isn't already an entry there.

Dtor call #1, Ctor Call #3:
= *pRoom; - This reassigns the previous "blank" Room_Data. Therefore calling its destructor, and then calling the copy constructor on Room_Data to copy *pRoom into the map. Note you don't notice the copy ctor because you're not logging the copy ctor in your Room_Data class


Dtor call #2:
/*rMap going out of scope*/ - This causes all elements to be destructed. Therefore the copy of the Room_Data that is in the map gets destructed


Note that there were 3 ctor calls and only 2 dtor calls. You just missed the copy ctor call because you're not watching the copy ctor... you're only watching the default ctor.

This is because 'pRoom' was never deleted. This is a memory leak.


EDIT:

Actually... I'm wrong. I don't think map would destruct then copy construct the object. I think it would just assign it.

Give me a minute to think about this a bit more.
Last edited on
Hello Disch thanks for your help

Is this the correct way to log with the copy ctor

Room_Data(const Room_Data &) {cout<<"Copy"<<endl;}

When i add this i get
Construct
Construct
Copy
Copy
Destruct
Destruct
2400
2400
Last edited on
judas97 wrote:
Is this the correct way to log with the copy ctor

Room_Data(const Room_Data &) {cout<<"Copy"<<endl;}

Yes.

But let's take a closer look at this...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <iostream>
#include <limits>
#include <map>
#include <cstring>
using namespace std;

class Room_Data
{
    public:

    Room_Data() {cout<<"Default Constructor of " << this <<endl;}

    Room_Data(const Room_Data & rd)
    {
        cout << "Copy Constructor of "<< this << " from " << &rd << endl;
        name=rd.name;
        vnum=rd.vnum;
    }

    Room_Data & operator=(const Room_Data & rd)
    {
        cout << this << '=' << &rd << endl;
        name=rd.name;
        vnum=rd.vnum;
    }

    ~Room_Data() {cout<<"Destructor of" << this <<endl;}

    char* name;
    int vnum;
};

int main()
{

    cout << "code block 1:\n" << endl;
    Room_Data *pRoom;
    pRoom = new Room_Data;
    pRoom->vnum = 2400;

    map<int, Room_Data> rMap;
    map<int, Room_Data>::reverse_iterator iter;

    cout << "\ncode block 2:\n" << endl;
    rMap[pRoom->vnum] = *pRoom;

    for ( iter = rMap.rbegin() ; iter != rMap.rend(); iter++ )
    {
        cout<<iter->second.vnum<<endl;
    }

    cout << "\ncode block 3:\n" << endl;
    cout<<pRoom->vnum<<endl;
    delete pRoom;
    cout << "Press ENTER to quit." << std::flush;
    std::cin.ignore( std::numeric_limits <std::streamsize> ::max(), '\n' );

    return 0;
}

As you see, this line here -> rMap[pRoom->vnum] = *pRoom; calls the default constructor once, the copy constructor twice, the destructor twice and the assignment operator once.

Disch wrote:
Remember that the [] operator will create an entry if there isn't already an entry there.

Yes, and to be more specific, a call to operator[] is equivalent to:

(*((this->insert(make_pair(x,T()))).first)).second

( http://cplusplus.com/reference/stl/map/operator%5B%5D/ )

The default constructor is obviously called to create the new entry in the map. The two copy constructor (and destructor) calls take place in the make_pair function:

1
2
3
4
5
template <class T1,class T2>
  pair<T1,T2> make_pair (T1 x, T2 y)
  {
    return ( pair<T1,T2>(x,y) );
  }

( http://cplusplus.com/reference/std/utility/make_pair/ )

The first when passing the arguments and the second when returning the pair.

The call of the assignment operator is also obvious.

EDIT: At least, that's what I think...
Last edited on
Okay, apparently std::map is doing lots of copies behind the scenes. I honestly don't know what they're all about.

Reprise:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Construct
pRoom = new Room_Data;

// Construct
// Copy
// Copy
// Destruct
// Destruct
rMap[pRoom->vnum] // the [] operator does all that

// just does an assignment, no ctors/dtors called
= *pRoom; 

// Destruct
 /* rMap going out of scope */


Note that rMap doesn't go out of scope until after you pause so you don't see that 'Destruct' line on the screen. You might be able to see it flash by after you hit Enter though.

As for why creating a new element with the [] calls the copy ctor twice... I have no idea. std::map just must be doing some weird things behind the scenes. It might not do that on every implementation though.


EDIT: ah! of course! I forgot that everything is in std::pairs. Yeah that might explain it. Good call @ roshi.
Last edited on
Thanks roshi for the explanation and links also for the debugging techniques you used (very helpful).

With all those calls to ctor when adding only 1 pointer to map mean its not an efficient or prefered way of storing them?

Ive change

rMap[pRoom->vnum] = *pRoom;

to

rMap.insert ( std::pair<int,Room_Data>(pRoom->vnum,*pRoom) );

that dont work :)
Last edited on
judas97 wrote:
Ive change

rMap[pRoom->vnum] = *pRoom;

to

rMap.insert ( std::pair<int,Room_Data>(pRoom->vnum,*pRoom) );

that dont work :)

It should be (*((rMap.insert(make_pair(2400,Room_Data()))).first)).second = *pRoom;
I tried it too and it calls one more copy constructor and destructor than the typical operator[] call... Don't really know what's going on... :/

judas97 wrote:
With all those calls to ctor when adding only 1 pointer to map mean its not an efficient or prefered way of storing them?

I wouldn't say so. It depends on what you want to do. If you just want to store items, use a vector or a deque or a list (look them up in the reference section of the site). A map is an associative container that stores elements in pairs of keys-values and automatically sorts the elements by their keys. It's designed so that you can access the values fast, using their keys. If that's what you want, map (or multimap) is the way to go.
Last edited on
Topic archived. No new replies allowed.