2

Can someone help me to understand how the std::map container is implemented? I have an class that contains atomic members and I have no need to invoke a copy constructor so I use the c++11 delete operator to suppress implicit generation of the copy constructor.

MyCalss(const MyClass& a) = delete; 

This has worked fine with my windows build, however in Linux I am greeted with an error informing me that the [] operator of the std::map class is trying to invoke a deleted function.

There seems to be a major difference between the Windows VS2013 and Linux GCC 4.7.x implementations of map. This led me to do an experiment regarding how an object is inserted into a map.

I wrote this small example program:

#include <stdlib.h> #include <stdio.h> #include <map> #include <iostream> #include <string> using namespace std; class TestItem { public: TestItem () { _name = "TestItem" + id(); cout << "Constructing " << _name << endl; } TestItem (const TestItem & other) { _name = "TestItem " + id(); cout << "Copying " << other._name << " to new " << _name <<endl; } string id() { static int id = 0; char buf[2]; sprintf_s(buf, "%d", id++); return string(buf); } ~TestItem(){ cout << "Destroying " << _name << endl; } void doStuff() { // stub } string _name; }; void run() { cout << "making new obj" << endl; TestItem a; cout << endl << endl; map<string, TestItem> TestItemMap; cout << "Makeing new obj as part of a map insert" << endl; TestItemMap["foo"].doStuff(); cout << endl << endl; cout << "adding a value to the map" << endl; TestItemMap["new foo key"] = a; cout << endl << endl; cout << "looking up a value that has already been inserted" << endl; TestItem& b = TestItemMap["foo"]; cout << endl << endl; } int main(int argc, char** argv) { run(); } 

In Windows when I run this program I get the following output:

making new obj Constructing TestItem0 Making new obj as part of a map insert Constructing TestItem1 adding a value to the map Constructing TestItem2 looking up a value that has already been inserted Destroying TestItem1 Destroying TestItem0 Destroying TestItem0 

This is what I would expect to see, internally when I write

 TestItemMap["foo"].doStuff(); 

I would expect that map would create a new instance of TestItem and then insert it in to the RedBlack Tree by internally linking the tree node to the new TestItem.

However when I run this same code in Linux the results are very different

making new obj Constructing TestItem0 Making new obj as part of a map insert Constructing TestItem1 Copying TestItem1 to new TestItem2 Copying TestItem2 to new TestItem3 Destroying TestItem2 Destroying TestItem1 adding a value to the map Constructing TestItem4 Copying TestItem4 to new TestItem5 Copying TestItem5 to new TestItem6 Destroying TestItem5 Destroying TestItem4 looking up a value that has already been inserted Destroying TestItem0 Destroying TestItem3 Destroying TestItem0 

This would indicate to me that the [] operator is creating a new instance of TestItem then calling the external map.insert() function and then destroying the newly created TestItem, and that only explains one of the calls to the copy constructor. Is the c++ stdlib in gcc really this inefficient?

Is there some standard trick that people use to overcome this problem?

11
  • 2
    The standard library used by default by GCC is called libstdc++ and is open source. Comes with the GCC source as well. If you want to know how it's implemented, just download the source and see it. Commented Nov 4, 2013 at 10:34
  • 6
    You are comparing both compilations with optimizations enabled, don't you? Commented Nov 4, 2013 at 10:36
  • 2
    As for your problem, I would be more curious about the two lines stating TestItem0 is destroyed. Commented Nov 4, 2013 at 10:39
  • 1
    sprintf_s is not standard so it does not compile on linux. What code did you use ? Commented Nov 4, 2013 at 10:40
  • 1
    @JoachimPileborg That is the result of the apparently unconsidered assignment operator used for TestItemMap["new mooky key"] = a;, which I bet the OP didn't see coming. Commented Nov 4, 2013 at 10:43

2 Answers 2

3

Firstly, I fixed up that horrible sprintf_s thing:

string id() { static int id = 0; std::stringstream s; s << id++; return s.str(); } 

and also changed your "looking up value that has already been inserted` to actually do what it says [EDIT: and so did you :-) ]

Now, compiling with g++ 4.8.1 in C++03 mode, I get a result similar to yours. But compiling with -std=c++11, I get

making new obj Constructing TestItem0 Making new obj as part of a map insert Constructing TestItem1 adding a value to the map Constructing TestItem2 looking up a value that has already been inserted Destroying TestItem0 Destroying TestItem1 Destroying TestItem0 

It seems that MSVC is automatically using C++11 features (move semantics most likely) to give a nice performance boost, whereas you need to explicitly tell g++ to do the same.

Sign up to request clarification or add additional context in comments.

Comments

2

It seems to be a bug that was fixed with GCC 4.8.

  • Here it works with GCC 4.8
  • Here it fails to compile with GCC 4.7

4 Comments

I daresay it's not a GCC bug, but instead MSVC is not working properly. An implicitly-declared move constructor is not generated in presence of a user-declared copy constructor. Therefore, GCC is not to blame for not being able to use move semantics -- user has told it not to. (don't ask me why version 4.8 "works", though)
@Damon The new element is default constructed in-place and then the value is assigned, I actually fail to see where the element is/should be copied. The bug, AFAICT, would be that GCC 4.7 required copy-constructibility internally while adding the new element when instantiating operator[], which is not required/allowed by the standard.
@Damon I think it's likely that it's an internal Node struct (or something similar) inside the std::map that is being move-constructed, with the effect that the user's copy-constructor is avoided.
@TristanBrindle No, neither copy- nor move-constructor should be needed, the node should use the default-constructor and construct the new element in place. This is what both VC and GCC 4.8 are doing. They then return a reference to this new element from operator[] and an assignment happens, see the examples I linked.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.