Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Insert elements into std::map without extra copying

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?

like image 283
Mischa Arefiev Avatar asked Dec 14 '12 14:12

Mischa Arefiev


People also ask

Does map insert make a copy?

Yes -- when you insert an item into an std::map, you pass it by value, so what it contains is a copy of what you passed.

Does insert overwrite map C++?

insert() doesn't overwrite.

How do I add a element to a std::map?

The standard solution to insert new elements into a map is using the std::map::insert function. It inserts the specified key-value pair into the map only if the key already doesn't exist. If the key already exists in the map, the element is not inserted.

What is the complexity of std::map :: insert () method?

Time complexity: k*log(n) where n is size of map, k is no. of elements inserted.


2 Answers

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.

like image 141
rici Avatar answered Nov 03 '22 18:11

rici


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.

like image 21
patros Avatar answered Nov 03 '22 19:11

patros