Insert elements into std::map without extra copying

12,879

Solution 1

Standard practice (with older C++ versions) where I've been is to use a Map of shared pointers.

Still creates a copy of the shared pointers, but that's usually much less onerous than copying large objects.

Solution 2

This is one of the many use cases which motivated C++11's move functionality, supported by a host of new features, particularly rvalue references, and a variety of new standard library interfaces, including std::map::emplace, std::vector::emplace_back, etc.

If, for whatever reason, you cannot yet use C++11, you can at least console yourself with the thought that the problem has been recognized, and that a solution has been standardized and implemented, and that furthermore many of us are using it, some of us [1] in production-code. So, as the old joke has it, a solution exists and it's your call as to when you take it up.

Note that you don't have to use the emplace member function if your objects implement move constructors, which they may even do by default. This won't happen if the have explicit copy constructors, so your test above may produce observer effects (and indeed, it might also suppress compiler optimizations in the case of PODs, so even with C++03 you might not have the problem you think you do).

There are a variety of hacks available which kinda-sorta avoid copies with only "minor" source code alterations, but IMHO the best approach is to start moving towards C++11. Whatever you do, try to do it in a way that will make the inevitable migration less painful.


[Note 1]: Disclaimer: I no longer write production code, having more or less retired, so I'm not part of the "some of us" in that sentence.

Solution 3

You can use emplace() :

The element is constructed in-place, i.e. no copy or move operations are performed. The constructor of the element type (value_type, that is, std::pair) is called with exactly the same arguments as supplied to the function

Share:
12,879
Mischa Arefiev
Author by

Mischa Arefiev

EXPERT BBCODE PROGRAMMER

Updated on June 04, 2022

Comments

  • Mischa Arefiev
    Mischa Arefiev almost 2 years

    Consider this program:

    #include <map>
    #include <string>
    #define log magic_log_function // Please don't mind this.
    
    //
    // ADVENTURES OF PROGO THE C++ PROGRAM
    //
    
    class element;
    typedef std::map<int, element> map_t;
    
    class element {
    public:
        element(const std::string&);
        element(const element&);
        ~element();
        std::string name;
    };
    element::element(const std::string& arg)
        : name(arg)
    {
        log("element ", arg, " constucted, ", this);
    }
    element::element(const element& other)
        : name(other.name)
    {
        name += "-copy";
        log("element ", name, " copied, ", this);
    }
    element::~element()
    {
        log("element ", name, " destructed, ", this);
    }
    int main(int argc, char **argv)
    {
        map_t map1; element b1("b1");
        log(" > Done construction.");
        log(" > Making map 1.");
        map1.insert(std::pair<int, element>(1, b1));
        log(" > Done making map 1.");
        log(" > Before returning from main()");
    }
    

    It creates some objects on stack and inserts them into an std::map container, creating two extra temporary copies in the process:

    element b1 constucted, 0x7fff228c6c60
     > Done construction.
     > Making map 1.
    element b1-copy copied, 0x7fff228c6ca8
    element b1-copy-copy copied, 0x7fff228c6c98
    element b1-copy-copy-copy copied, 0x232d0c8
    element b1-copy-copy destructed, 0x7fff228c6c98
    element b1-copy destructed, 0x7fff228c6ca8
     > Done making map 1.
     > Before returning from main()
    element b1 destructed, 0x7fff228c6c60
    element b1-copy-copy-copy destructed, 0x232d0c8
    

    We can get rid of one extra copy constructor call by changing the std::pair signature to std::pair<int, element&>, however, the second temporary is still created and immediately destroyed:

    element b1 constucted, 0x7fff0fe75390
     > Done construction.
     > Making map 1.
    element b1-copy copied, 0x7fff0fe753c8
    element b1-copy-copy copied, 0x1bc4098
    element b1-copy destructed, 0x7fff0fe753c8
     > Done making map 1.
     > Before returning from main()
    element b1 destructed, 0x7fff0fe75390
    element b1-copy-copy destructed, 0x1bc4098
    

    Is there a way to make std::map just take an object on stack by reference and make a single internal copy of it?