Search code examples
c++stlconstructorcopy-constructortemporary-objects

In an STL Map of structs, why does the "[ ]" operator cause the struct's dtor to be invoked 2 extra times?


I've created a simple test case exhibiting a strange behavior I've noticed in a larger code base I'm working on. This test case is below. I'm relying on the STL Map's "[ ]" operator to create a pointer to a struct in a map of such structs. In the test case below, the line...

TestStruct *thisTestStruct = &testStructMap["test"];

...gets me the pointer (and creates a new entry in the map). The weird thing I've noticed is that this line not only causes a new entry in the map to be created (because of the "[ ]" operator), but for some reason it causes the struct's destructor to be called two extra times. I'm obviously missing something - any help is much appreciated! Thanks!

#include <iostream>
#include <string>
#include <map>

using namespace std;
struct TestStruct;

int main (int argc, char * const argv[]) {

    map<string, TestStruct> testStructMap;

    std::cout << "Marker One\n";

    //why does this line cause "~TestStruct()" to be invoked twice?
    TestStruct *thisTestStruct = &testStructMap["test"];

    std::cout << "Marker Two\n";

    return 0;
}

struct TestStruct{
    TestStruct(){
        std::cout << "TestStruct Constructor!\n";
    }

    ~TestStruct(){
        std::cout << "TestStruct Destructor!\n";
    }
};

the code above outputs the following...

/*
Marker One
TestStruct Constructor!             //makes sense
TestStruct Destructor!               //<---why?
TestStruct Destructor!               //<---god why?
Marker Two
TestStruct Destructor!               //makes sense
*/

...but I don't understand what causes the first two invocations of TestStruct's destructor? (I think the last destructor invocation makes sense because testStructMap is going out of scope.)


Solution

  • The functionality of std::map<>::operator[] is equivalent to

    (*((std::map<>::insert(std::make_pair(x, T()))).first)).second
    

    expression, as specified in the language specification. This, as you can see, involves default-constructing a temporary object of type T, copying it into a std::pair object, which is later copied (again) into the new element of the map (assuming it wasn't there already). Obviously, this will produce a few intermediate T objects. Destruction of these intermediate objects is what you observe in your experiment. You miss their construction, since you don't generate any feedback from copy-constructor of your class.

    The exact number of intermediate objects might depend on compiler optimization capabilities, so the results may vary.