Search code examples
c++11insertperfect-forwardingemplace

How to insert / emplace into map to avoid creation of temporary objects?


I'm reading a recommendation from one of C++11 books to prefer emplace over insert when adding items to the container in order to avoid creation of temporary objects (calls of constructors / destructors of the objects being inserted). But I'm a bit confused because there are a few possibilities how one can add an object to the map, e.g.

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

int main()
{
    std::string one     { "one" };
    std::string two     { "two" };

    std::map<uint32_t, std::string> testMap;

    testMap.insert(std::make_pair(1, one));         // 1
    testMap.emplace(2, two);                        // 2
    testMap.insert(std::make_pair(3, "three"));     // 3
    testMap.emplace(4, "four");                     // 4

    using valType = std::map < uint32_t, std::string >::value_type;
    testMap.emplace(valType(5, "five"));            // 5
    testMap.insert(valType(6, "six"));              // 6

    return 0;
}

There are also some under-the-hood-mechanisms involved which are not immediately visible when reading such a code - perfect forwarding, implicit conversions ...

What is the optimal way of adding items to the map container ?


Solution

  • Let's consider your options one at a time (plus one or two you haven't mentioned).

    Options 1 and 6 are essentially identical as far as semantics go. The using and the pair are just two different ways of spelling the value_type of the map. If you wanted you could add a third way using a typedef instead of a using statement:

    typedef std::map<uint32_t, std::string>::value_type valType;
    

    ...and have a C++98/03 equivalent of your #6. All three end up doing the same thing though: creating a temporary object of the pair type, and inserting that into the map.

    Versions 3 and 5 do pretty much the same. They use emplace, but what they pass is already an object of the map's value_type. By the time emplace itself starts to execute, the type of object that will be stored in the map has already been constructed. Again, the only difference between the two is in the syntax used to specify that pair type--and, again, with a typedef like I've shown above, you could have a C++98/03 equivalent of the one that currently has the using statement. The fact that version 3 uses insert and version 5 uses emplace makes almost no real difference--by the time either member function is invoked, we've already created and passed a temporary object.

    Options 2 and 4 both actually use emplace more like it was probably intended--passing individual components, perfect-forwarding them to the constructor, and constructing a value_type object in-place, so we avoid creating any temporary objects at any point. The primary (sole?) difference between the two of them is in whether the thing we pass for the string component of the value_type is a string literal (from which a temporary std::string object needs to be created) or a std::string object that was created ahead of time.

    The choice between those could be non-trivial. If (as above) you're only doing it once, it won't really make any difference at all--regardless of when you create it, you're creating a string object, then putting it into the map.

    So, to make a real difference, we need to pre-create the string object, then repeatedly insert that same string object into the map. That's pretty unusual in itself--in most cases, you're going to do something like read external data into a string, then insert that into the map. If you really do insert (an std::string constructed from) the same string literal repeatedly, chances are pretty good that any reasonable compiler can detect that the resulting string is loop-invariant, and hoist the string construction out of the loop, giving essentially the same effect.

    Bottom line: as far as the use of map itself goes, choices 2 and 4 are equivalent. Between those two, I wouldn't go to any real effort to use option 2 over option 4 (i.e., pre-creating the string) but it's likely to happen most of the time, simply because inserting a single string literal into a map is rarely useful. The string you put in a map will much more frequently come from some external data source, so you'll have a string because that's what (for example) std::getline gave you when you read the data from the file.