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
152 views
in Technique[技术] by (71.8m points)

c++ - What does it mean that the emplacement function adds a new element to a container via construction in place istead of via assignment?

In Item 41 from Effective Modern C++ Scott Meyers mentions this difference and its impact on the efficiency of emplacement with respect to insertion.

I have some doubts about that, but before asking questions about that I need to understand what the difference between these two ways of adding an element is.

Consider the code sample from the book:

std::vector<std::string> vs;
// adding some elements to vs
vs.emplace(v.begin(), "xyzzy");

It's clear that after // adding some elements to vs, it could be that

  • vs.capacity() == vs.size(), or
  • vs.capacity() > vs.size().

Correspondingly,

  • vs has to reallocate, and all pre-existing elments in vs (those named vs[0], vs[1], ... before the reallocation takes place) have to be move-constructed to the new memory locations (the new locations of vs[1], vs[2], ...)
  • vs has to vs.resize(vs.size() + 1) and all pre-existing elements have to be move-assigned to the next index, obviously processing backward, from the last to the first element.

(Obviously I referred to move operations because std::string offers noexcept move operations. If this is not the case, then the scenario above is slightly different and copy operations will be used instead.)

However, going to the crux of my question, right after the code above, the book reads (my italic)

[…] few implementations will construct the added std::string into the memory occupied by vs[0]. Instead, they'll move-assign the value into place. […]

What are the two scenarios, after the room has been done to accomodate the new element?

  • If emplace adds the element via move assignment, it means it does v[0] = std::string{strarg}; where strarg == "xyzzy", right?
  • But what is the other case of contructing the element occupied by v[0]? Is it a placement new? And how would it look like? I guess it should be similar to the chunk of code at the section Placement new here, but I'm not sure how it would look like in this case.
question from:https://stackoverflow.com/questions/65650868/what-does-it-mean-that-the-emplacement-function-adds-a-new-element-to-a-containe

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

1 Reply

0 votes
by (71.8m points)

There are many different ways to implement emplace, and the standard is pretty lax on how implementations must do it.

Given a pointer to somewhere allocated by std::allocator_traits<allocator_type>::allocate, the only way for a vector to construct a new object is with std::allocator_traits<allocator_type>::construct. For the default allocator, this will call placement new.

Now if a reallocation did occur, the obvious way to emplace the new element is to call allocator_traits::construct(get_allocator(), ptr, std::forward<Args>(args...)). This will be the equivalent of new (ptr) std::string("xyzzy"). But note that all other elements were also move constructed to the new buffer via allocator_traits::construct(get_allocator(), ptr, std::move(old_ptr)).

If a reallocation didn't occur, most implementations will just construct an element with value_type(std::forward<Args>(args...)) and move-assign from that (equivalent to v[0] = std::string("xyzzy")). This is what libstdc++ does.

Alternatively, instead of move constructing v[0] = std::string("xyzzy"), the object can be destroyed via allocator_traits::destroy ((&v[0])->~value_type() for the default allocator), and then it can be constructed in place via allocator_traits::construct. This seems like it would be harder to implement since special care would need to be taken to ensure that the element isn't destroyed twice if the move constructor throws, which is probably why only "few implementations" will do it.

As an aside, there is no strong exception guarantee, so move_if_noexcept doesn't have to be used and the move constructor may always be called, even if the move constructor is not noexcept.


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

...