Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
238 views
in Technique[技术] by (71.8m points)

c++ - Does map move-insertion guarantee that elements are or are not moved from?

The standard "map" containers in C++ allow you to insert an rvalue:

T x;

std::map<int, T> m;

// m[1];  // populate "1"

auto it = m.insert(std::make_pair(1, std::move(x)));

The question is what happens when the element already exists, i.e. it->second == false. Has the element x already been "moved-from"? For instance, if it is a unique pointer, will x have been reset to null?

Patently the answer in the above case is "yes", because the moving-from happens already at the point of creating the pair. But suppose now I want to update the existing value, but still retain the information of whether the value already existed or not (so I can't just say m[1] = std::move(x);). Is it possible to "not move from" the object in that case?

I discovered in GCC that the following works [Update: works in GCC 4.6, does not work in GCC 4.8]:

auto it = m.insert(std::pair<const int, T &&>(1, std::move(x)));

But is this guaranteed to not move?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Though std::move does not actually perform any move, and neither does std::make_pair, std::make_pair forwards its arguments to the std::pair constructor, which initialises its two value members from those arguments.

As such, the move is performed at that point, before the std::map has a chance to do anything. So, yes, you end up with a "broken" move for no good reason.

You should be able to take advantage of emplace (in order to skip the pair construction). From Table 102:

Effects: Inserts a T object t constructed with std::forward<Args>(args)... if and only if there is no element in the container with key equivalent to the key of t.

Clearly, the library is still "forwarding" at this point so it's pre-move, and in your case no emplace shall take place so the entire expression ought to be an effective no-op.

However, libstdc++ from GCC 4.8.0 appears to have a bug in this regard: emplace invokes _M_emplace_unique on the internal tree, which forwards the arguments to _M_create_node, which forwards the arguments to allocator_traits<_Node_allocator>::construct, which forwards the arguments to _S_construct, which forwards the arguments to __a.construct which, with the default allocator, is std::allocator<std::pair<const _Key, _Tp> >::construct, which is the pair constructor you were trying to avoid... all before the collision check in _M_emplace_unique.

It could be claimed that the standard is ambiguous in this regard, but I'd call it a violation of intent. Then again, clang v3.4 with libc++ exhibits this behaviour too, as does Visual Studio 2012. So if my standard interpretation is correct, this fails on all three of the mainstream toolchains.

I guess they all decided that the "if and only if" applied to the insertion, rather than the insertion and the construction.

I have posted a question on std-discussion aiming to provoke an improvement to the passage from Table 102 to authoritatively answer this once and for all.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...