问题
I defined the following
std::vector<std::pair<int,int> > my_vec;
my_vec.push_back( {1,2} ); //this works
my_vec.emplace_back( {1,2} ); // this doesn't work
std::pair<int,int> temp_pair = {1,2};
my_vec.emplace_back( temp_pair ); //this works
I am compiling with c++11. The third line is problematic, but I thought you can use emplace_back()
anywhere that you have push_back()
, but this is apparently wrong. Why does the third line not work?
回答1:
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 havepush_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?
回答2:
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 int
→double
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.
来源:https://stackoverflow.com/questions/53900865/emplace-back-vs-push-back-when-inserting-a-pair-into-stdvector