Reputation: 22275
The best way to explain what I'm trying is accomplish is with this example (compiled with Visual Studio 2008 SP1
):
struct ELEMENT1{
//Its members
ELEMENT1()
{
//Constructor code
}
~ELEMENT1()
{
//Destructor code
}
};
std::map<std::wstring, ELEMENT1> map;
std::pair<std::map<std::wstring, ELEMENT1>::iterator, bool> resIns;
ELEMENT1 element;
std::wstring strKey;
for(size_t i = 0; i < numberRepetitions; i++)
{
//Do processing
//...
//set 'strKey'
//Insert new element into the map first
resIns = map.insert(std::pair<std::wstring, ELEMENT1>(strKey, element)); //This line calls ELEMENT1 constructor & destructor twice
//Then fill out the data
fill_in_data(resIns.first->second);
}
BOOL fill_in_data(ELEMENT1& outInfo)
{
//Fill in 'outInfo' -- MUST be in its own function
//...
}
My goal is to optimize this code, and thus I did the following:
Moved ELEMENT1 element
construction/destruction outside of the loop.
I'm inserting the element
into the map
and then attempt to fill it out using the pointer to the inserted element instead of constructing new element, then filling it out, then copying it into the map, and then destroying it. (At least that was the plan.)
But when I compile this for a Release
build and check the assembler code, I can see that the C++ line with map.insert()
function calls ELEMENT1
constructor twice! and then twice its destructor. So the following machine code is just for that map.insert()
line:
So I'm obviously not seeing something here.
Can someone suggest what's going on in that compiled code & if it's possible to optimize it?
Upvotes: 2
Views: 771
Reputation: 27766
resIns = map.insert(std::pair<std::wstring, ELEMENT1>(strKey, element));
You are constructing a temporary std::pair
whose member second
is a ELEMENT1
. This causes the copy constructor of ELEMENT1
to be called.
The 2nd call to the copy constructor of ELEMENT1
is when std::map::insert()
creates a new element in the map that will be initialized by the temporary std::pair
.
You can avoid the duplicate constructor call caused by the temporary by using std::map::operator[]
instead:
ELEMENT1& resIns = map[ strKey ];
fill_in_data( resIns );
If strKey
doesn't already exist in the map, an ELEMENT1
will be default-constructed directly within the map and a reference to the new object will be returned. The constructor will be called exactly one time.
If strKey
already exists in the map, a reference to the existing object will be returned.
Upvotes: 1
Reputation: 180630
The reason you have 2 constructor calls is because what you are passing to insert
does not match what it need. std::map::insert
takes a const value_type&
and value_type
for a map is
std::pair<const key_type, element_type>
^^^^^ this is important
So, since they do not match you construct one element when you use
std::pair<std::wstring, ELEMENT1>(strKey, element)
and then the compiler calls the copy constructor to convert that into a
std::pair<const std::wstring, ELEMENT1>
A quick fix is to change the code to
std::pair<const std::wstring, ELEMENT1>(strKey, element)
Which leaves you with one temporary that is constructed and destructed. You can also do as zett42 suggests in their answer to avoid the creation of the temporary entirely.
Upvotes: 2
Reputation: 37547
You should use emplace to avoid creation on temp objects:
resIns = map.emplace
(
::std::piecewise_construct
, ::std::forward_as_tuple(strKey)
, ::std::forward_as_tuple()
);
A good reason to switch to newer VS version.
Upvotes: -1