Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What happened when call std::map's operator[] or insert

I have following code:

#include <functional>   // std::less
#include <map>
#include <iostream>
using namespace std;

class Key
{
public:
        Key() {cout << "Key Constructor" << endl;}
        ~Key() {cout << "Key Destructor" << endl;}
        Key(const Key& key) {cout << "Key Copy Constructor" << endl;}

        bool operator < (const Key& k1) {return true;}
};
int main()
{
        map<Key, int> mymap;
        Key k;

        cout << "operator[]"<<endl;
        mymap[k] = 1;

        map<Key, int> mymap2;
        cout << "insert"<<endl;
        mymap2.insert(std::make_pair(k, 1));
        cout << "=========" << endl;

}

And the output is:

$ g++ test.cpp -fpermissive
$ ./a.out
Key Constructor
operator[]
Key Copy Constructor
Key Copy Constructor
Key Destructor
insert
Key Copy Constructor
Key Copy Constructor
Key Copy Constructor
Key Copy Constructor
Key Destructor
Key Destructor
Key Destructor
=========
Key Destructor
Key Destructor
Key Destructor

Could anyone please explain why mymap[k] = 1; invoke 2 copy constructor and mymap2.insert(std::make_pair(k, 1)); invokes 4 copy constructor? and does that mean operator[] is much more efficient than insert?

Thanks.

Summary:

Thanks user 6502 and petersohn for your insight, I now guess the reason for the 2 extra copy constructor for insert is as below:

  • make_pair is a function, it make a copy inside function first, and then return the copy - that is one extra copy
  • make_pair(k, 1) will create a pair<Key, int>, but the required value_type is pair<const& Key, int>, the type conversion will cause another extra copy

So in case 2, if I use:

mymap2.insert(std::pair<const Key, int>(k, 1));

The number of copy constructors get called will be the same with operator[]

As 6502 noted, following claim has been changed hence not true anymore:

A call to this function(operator[]) is equivalent to: (*((this->insert(make_pair(x,mapped_type()))).first)).second

operator[] is implemented different to avoid extra copy introduced by make_pair()

like image 744
Baiyan Huang Avatar asked Jun 22 '13 06:06

Baiyan Huang


People also ask

What is the complexity of std::map :: insert () method?

Time complexity: k*log(n) where n is size of map, k is no. of elements inserted.

Does insert overwrite map C++?

insert() doesn't overwrite.

How do you insert a std::map?

The standard solution to insert new elements into a map is using the std::map::insert function. It inserts the specified key-value pair into the map only if the key already doesn't exist. If the key already exists in the map, the element is not inserted.

What is map insert return C++?

Return Value: The function returns a pair, with its member pair::first set to an iterator pointing to either the newly inserted element or to the element with an equivalent key in the map. The pair::second element in the pair is set to true if a new element was inserted or false if an equivalent key already existed.


2 Answers

The problem with insert is that make_pair will create a pair of the wrong type so the passed object will need a conversion to a proper pair type to be passed to insert.

Actually in a former version the C++ standard mandated map::operator[] to behave as insert (thus forcing an inefficient implementation). Later the text was relaxed allowing a better implementation.

Note anyway that for standard containers the elements are considered to be "values", i.e. the implementation is free to copy things around and you get no guarantees about how many copies will be made.

You can see the make_pair problem by changing the code to

    mymap2.insert(std::pair<const Key, int>(k, 1));

Longer explanation

The problem is that make_pair will create an std::pair<Key, int> value but insert signature wants instead a const std::pair<const Key, int>& (note that std::map::value_type is a pair with a const first element).

These two types are incompatible and unrelated so to be able to make the call another pair must be created copying both key and value and this is where an extra key duplication occurs.

Even if it could be apparently "logical" that a pair<X, Y> should be directly usable where pair<const X, Y> is expected this is not true in C++ and it's one of the logical problems of the const-correctness concept (it doesn't scale by composition).

like image 141
6502 Avatar answered Oct 30 '22 12:10

6502


It's because of make_pair. You have to copy k into the pair, plus there is an extra function call. Possibly the number of copies can be reduced by enabling optimization (I didn't try). However, if you do this, then you'll have exactly the same number of copies than with operator[]:

mymap2.insert(std::pair<const Key&, int>(k, 1));

In C++11 though, things start to get better. If you compile your code with C++11, then you'll get two copies with insert. Even better, if Key has a move constructor, you'll get one copy and one move for insert (while two copies with operator[]). If you use my line above in C++11, you'll even spare the move and only get one copy.

Interestingly, with operator[], I always get two copies, for which I don't know the exact reason.

like image 22
petersohn Avatar answered Oct 30 '22 13:10

petersohn