Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::map emplace/insert moving value being inserted

Tags:

c++

map

c++14

Currently I'm reading C++1y papers, for now I'm trying to understand the n3873 paper titled Improved insertion interface for unique-key maps. The paper states that there's a problem with insert and emplace methods, it illustrates the problem with the following example:

std::map<std::string, std::unique_ptr<Foo>> m;
m["foo"];

std::unique_ptr<Foo> p(new Foo);
auto res = m.emplace("foo", std::move(p));

And after the code above, it express the following:

What is the value of p? It is currently unspecified whether p has been moved-from. (The answer is that it depends on the library implementation.)

Well, I'm having troubles while looking for the explanation of the previous quote, mainly because I'm unable to find where in the standard is specified that in a code like the above to move or not to move p is implementation defined; looking to the n3690 standard associative containers section (23.2.4) about the emplace(args) (Inserts a value_type object t constructed with std::forward<Args>(args)) and insert(t) methods only mentions that the value is inserted or emplaced...

... if and only if there is no element in the container with key equivalent to the key of t.

Not a word about moving (or not) the t value; on the other hand, the p managed memory is freed anyways (if it is moved p is freed after the no-insertion, and if isn't moved is freed ad the end of the scope) isn't it?


After the introduction, let me ask the following questions:

  • Why moving a value while inserting/emplacing it into an associative container which already have the inserted key, sets the value in an unspecified state?
  • Where is worded that this operation is implementation-defined?
  • What happens with the p of the example? Is it really freed?

Please, try to forgive if the question looks silly or with an obvious answer, it could be due my lack of english understanding skills or because I'm not used to dive into the standard papers. Any guidance would be appreciated.

like image 922
PaperBirdMaster Avatar asked Jun 11 '14 06:06

PaperBirdMaster


People also ask

How to use map insert () in C++ STL?

map insert () in C++ STL. Last Updated: 12-07-2018. The map::insert () is a built-in function in C++ STL which is used to insert elements with a particular key in the map container. Syntax: iterator map_name.insert ( {key, element})

What is the difference between insert () and emplace () in MAP2?

2. Using emplace: emplace is also used to insert the pairs into the map. This function is similar to “insert ()” discussed above, the only difference being that “in-place” construction of pair takes place at the position of element insertion contrary to insert () which copies or movies existing object.

What is the use of insert in map?

insert (beg_ptr, end_ptr) : This type of insertion is required to insert the pairs of other container into map. The repeated pairs are not inserted if they are present in the destination container. Time complexity: k*log (n) where n is size of map, k is no. of elements inserted.

What happens to the map after insertion?

After insertion, the reordering of elements takes place and the map is sorted w.r.t the key. This function is implemented in 3 ways: insert (pair): This function inserts the pair in the map.


2 Answers

Move semantics in c++ are not related to emplace/insert methods. The latter are just one of the cases which uses move semantics to gain performance.

You should learn about rvalue references and move semantics in order to understand why p has undefined value after the line "m.emplace("foo", std::move(p));"

You can read in in detail for example here: http://www.slideshare.net/oliora/hot-c11-1-rvalue-references-and-move-semantics

In short, std::move(p) statement tells compiler that you do not care about p's contents anymore and totally okey that they will be moved somewhere else. In practice, std::move(p) converts p to rvalue reference type (T&&). rvalue existed in c++ before c++11 without having the "official" type. For example expression (string("foo") + string("bar")) produces rvalue which is a string with an allocated buffer containing "foobar". Before c++11 you could not use the fact that this expression is totally temporary and is going to vanish in a second (besides in compiler optimizations). Now you get this as part of the language:

v.emplace_back(string("foo") + string("bar"))

is going to take the temporary string and move its contents directly into the container (no redundant allocations).

It works elegantly with temporary expressions but you can not do it directly with variables (which are the opposite of rvalues). However, in some cases you know that you do not need this variable anymore and you want to move it some where else. For that you use std::move(..) which tells the compiler to treat this variable as an rvalue. You need to understand that you can not use it afterwards. That is the contract between you and the compiler.

like image 167
Roman Avatar answered Sep 22 '22 15:09

Roman


Not a word about moving (or not)

This is precisely the problem, it's left unspecified under what conditions the mapped_type will be moved.

Why moving a value while inserting/emplacing it into an associative container which already have the inserted key, sets the value in an unspecified state?

There's nothing preventing an implementation from moving the unique_ptr into a temporary variable first, and then searching for the key "foo". In this case, regardless of whether the map already contains the key or not, p == nullptr when the call to emplace returns.

Conversely, an implementation could conditionally move depending on whether the key exists or not. Then, if the key exists, p != nullptr when the function call returns. Both methods are equally correct, and in the first case there's no way to retrieve the original contents of p even if the insertion never takes place, it will be destroyed by the time emplace returns.

The proposed emplace_stable() and emplace_or_update() functions are to make the behavior predictable under all circumstances.

Where is worded that this operation is implementation-defined?

It's not specified as implementation defined, it's under specified, allowing implementations too much latitude, potentially resulting in behavior that's not always desirable.

What happens with the p of the example? Is it really freed?

In the example you've shown the contents of p will not be inserted into the map (since the key "foo" already exists). But p may or may not be moved from when the call to emplace returns.

There will never be a resource leak in any case. If the implementation unconditionally moves p it'll move it into a local copy, which will either be destroyed if the key exists, or inserted into the map if the key doesn't exist.

On the other hand, if the implementation conditionally moves p, it'll either be inserted into the map, or p will own it when emplace returns. In the latter case, it'll, of course, be destroyed when p goes out of scope.

like image 40
Praetorian Avatar answered Sep 18 '22 15:09

Praetorian