7

I was going through Josuttis's "Using Map's as associative arrays" (from The C++ Standard Library - A Tutorial and Reference, 2nd Edition) and came across Using a std::map as an associative array on Stack Overflow. Now I have more questions on the constructors that are called when inserting into a map.

Here is my sample program (not using best coding practices; please excuse me for that):

class C
{
public:

   string s;

   C() { cout << "default " << endl;}

   C(const string& p) : s(p)
   { cout << "one param" << endl;}

   C(const C& obj)
   {
       if (this != &obj)
       {
         s = obj.s;
       }
       cout << "copy constr" << endl;
   }

   C& operator  = (const C& obj)
   {
       if (this != &obj)
       {
             s = obj.s;
       }
      cout << "copy initializer" << endl;
      return *this;
   }
};

int main()
{
    map<int,C> map1;
    C obj("test");

    cout << "Inserting using index" << endl;
    map1[1] = obj;

    cout << "Inserting using insert / pair" << endl;
    map1.insert(make_pair(2,obj));
}

The output for this program is:

one param
Inserting using index
default
copy constr
copy constr
copy initializer
Inserting using insert / pair
copy constr
copy constr
copy constr
copy constr

I was assuming that initializing the map by index should call the default constructor and followed by the assignment operator.

But executing map1[1] = obj creates following output;

Inserting using index
default
copy constr
copy constr
copy initializer

Can someone help me to understand the initialization better?

Community
  • 1
  • 1
mithuna
  • 1,011
  • 1
  • 9
  • 9
  • You dont need to test for copy construction to self. – Martin York Nov 17 '09 at 21:26
  • As a matter of fact, now i understand it is better to have pointers than objects in STL containers. – mithuna Nov 17 '09 at 22:16
  • 1
    @mithuna: Warning: Storing pointers instead of objects brings lifetime management issues, so I'd say that it is better to store objects. *If* this causes performance issues, you should see if the copy can be optimized. Then, *if* it can't, you could consider storing pointers instead of objects. In that case, you may be interested in the Boost.PointerContainer library (http://www.boost.org/doc/libs/1_40_0/libs/ptr_container/doc/ptr_container.html). ptr_map (http://www.boost.org/doc/libs/1_40_0/libs/ptr_container/doc/ptr_map.html), in the present case. – Éric Malenfant Nov 17 '09 at 22:27
  • ... or, if you have access to one, use an implementation that supports C++0x move semantics. – Éric Malenfant Nov 17 '09 at 22:30

4 Answers4

8

If you read the specification for std::map, it says that operator[] is equivalent to (in this case)

(*((this->insert(make_pair(1,C()))).first)).second

So this explains all the constructor calls you see. First it calls the default constructor C(). Then it calls make_pair, which copies the C object. Then it calls insert, which makes a copy of the pair object you just made, calling the C copy constructor again. Finally it calls the assignment operator to set the inserted object to the one you are assigning it to.

Andrey
  • 808
  • 2
  • 6
  • 12
  • Same as my answer, only better! +1 and deleted mine. – Éric Malenfant Nov 17 '09 at 21:59
  • Nice response, re-read the spec and it did make lot of sense now. – mithuna Nov 17 '09 at 22:11
  • Is it reasonable to assume that with some -O option we might only get one Copy Construction call ? Note that for better performance you could use `iterator insert(iterator pos, value_type const& x)` where `pos` can be deduced using `iterator lower_bound(key_type const&)` (and taking its predecessor if this is valid). You would thus avoid default construction and assignment while not falling into the double search trap. – Matthieu M. Nov 18 '09 at 09:57
2

Don;t know. But this is interesting:

#include <string>
#include <map>
#include <iostream>
using namespace std;

class C
{
    public:
        string s;
        C()
        {
            cout << "default " << endl;
        }
        C(const string& p)
        : s(p)
        {
            cout << "one param(" << s << ")" << endl;
        }
        C(const C& obj)
            :s(obj.s)
        {
           cout << "copy constr(" << s << ")" <<endl;
        }
        C& operator  = (const C& obj)
        {
            cout << "copy initializer\t" <<;

            C copy(obj);
            std::swap(s,copy.s);

            return *this;
        }
};

int main()
{
    map<int,C> map1;
    cout << "Inserting using index" << endl;
    map1[1] = C("Plop");
}

It looks like the default one is created and copied around.
Then the external one is just assinged over it once it has been put in place.

Inserting using index
default
copy constr()
copy constr()
one param(Plop)
copy initializer      copy constr(Plop)
Martin York
  • 257,169
  • 86
  • 333
  • 562
0

What happens if you simply execute map[1];? This may involve internal copies, depending on the implementation of map your standard library uses.

coppro
  • 14,338
  • 5
  • 58
  • 73
0

Actually map1[1] = obj will create pair first

BostonLogan
  • 1,576
  • 10
  • 12