emplace_back() vs push_back when inserting a pair into std::vector

[亡魂溺海] 提交于 2019-12-06 02:50:19
bolov

emplace_back takes a variadic parameter pack as argument:

template< class... Args >
reference emplace_back( Args&&... args );

When you call it like this: emplace_back({1, 2}) you are calling it with one argument i.e. {1, 2} and the Args cannot be deduced. That is because of how the language has evolved. In C++ {1, 2} doesn't have a type. It is a brace enclosed init-list and can be used in certain types of initializations, but all require the type of the initialized to be known. That is why temp_pair = {1,2}; works, because the type of temp_pair is know and has a constructor matching (int, int).

Anyway emplace_back wasn't supposed to be used like that, but like this instead:

my_vec.emplace_back(1, 2);

Also please note that even if these work:

my_vec.emplace_back(std::pair<int, int>{1, 2});
my_vec.emplace_back(temp_pair);   

They shouldn't be used. They add no advantage over push_back. The whole point of emplace_back is to avoid creating a temporary T. The above calls all create the temporary std::pair<int, int>.


but I thought you can use emplace_back() anywhere that you have push_back()

For the most part that's correct. At least that was the intention. And you can indeed use it in your cese. You just have to adjust the syntax a little. So instead of push_back({1, 2}) you can use emplace_back(1, 2).

There is a situation where unfortunately you can't use emplace_back: aggregates.

struct Agg
{
    int a, b;
};

auto test()
{
    Agg a{1, 2}; // ok, aggregate initialization

    std::vector<Agg> v;
    v.emplace_back(1, 2); // doesn't work :(
}

This doesn't work unless you add a constructor for Agg. This is considered an open defect in the standard, but unfortunately they can't find a good solution to this. The problem is with how brace init initialization works and if you use it in generic code you can miss some constructors. For all the nitty-gritty details check this great post: Why can an aggreggate struct be brace-initialized, but not emplaced using the same list of arguments as in the brace initialization?

1) {1, 2} is not an expression

The syntax

{1, 2}

is very "strange" compared to other things in C++.

Normally in C++ you have an expression (e.g. x + 1.2) and the expression has a deduced type... for example if x is an int variable the type of the expression will be double because of implicit conversion intdouble and how addition works.

Now back to {1, 2}: this is "strange" because despite looking like an expression it's not... it's just syntax and its meaning will depend on where it's used.

In a sense typing here will work the opposite of most C++ places: normally in C++ it's "in"→"out" (the type "emerges" from the components) but here is "out"→"in" (the type is "injected" in the components).

The text {1, 2} just doesn't mean enough by itself to be compiled (it can mean different things depending on where it is used).

All this boils down to the fact that {1, 2} cannot be used exactly like an expression, even if the rules are carefully designed to trick you into thinking it does.

2) emplace_back accepts constructor parameters

emplace_back was designed to be able to build the object directly inside the final place in the container... the expected parameters are parameters of a constructor and this is done to avoiding creating a temporary object just to be able make a copy for the final destination and then throwing it away. The expected parameter of emplace_back are therefore 1 and 2... not a single thing because NOT building a temporary single thing is exactly the reason emplace_back was designed for.

You can pass emplace_back an instance because the contained type has a copy constructor and the instance is considered a parameter for the copy (move) constructor, not an object to be copied (moved) into destination (what push_back expect). The operations performed in this case are the same, but the point of view is different.

Consequences

To summarize: emplace_back cannot use {1, 2} because it can accept anything (so doesn't provide enough "context") and that syntax has not enough meaning. push_back instead can accept it because it expects a specific type and this provides enough context for interpreting the syntax {1, 2}. This is a simplified explanation but, as usual, C++ went into a direction of even more parsing complexity and special cases, so I can understand why things are not clear for you.

The key point however is that emplace_back was not meant to take a full object... for that use push_back. The new emplace_back construct should be used when you want to pass the CONSTRUCTOR PARAMETERS for building the final object in the container.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!