Can I break a valid C++03 program by replacing std::vector::push_back
with emplace_back
and compiling it with C++ 11 compiler? From reading emplace_back
reference I gather it shouldn't happen, but I'll admit I don't fully get rvalue references.
I constructed a short example that actually fails to compile when push_back
is replaced by emplace_back
:
#include <vector>
struct S {
S(double) {}
private:
explicit S(int) {}
};
int main() {
std::vector<S>().push_back(0); // OK
std::vector<S>().emplace_back(0); // error!
}
The call to push_back
needs to convert its argument 0
from type int
to type S
. Since this is an implicit conversion, the explicit constructor S::S(int)
is not considered, and S::S(double)
is called. On the other hand, emplace_back
performs direct initialization, so both S::S(double)
and S::S(int)
are considered. The latter is a better match, but it's private
, so the program is ill-formed.
Yes, you can change the behavior (more than just avoiding a copy constructor call), since emplace_back
only sees imperfectly forwarded arguments.
#include <iostream>
#include <vector>
using namespace std;
struct Arg { Arg( int ) {} };
struct S
{
S( Arg ) { cout << "S(int)" << endl; }
S( void* ) { cout << "S(void*)" << endl; }
};
auto main()
-> int
{
vector<S>().ADD( 0 );
}
Example builds:
[H:\dev\test\0011] > g++ foo.cpp -D ADD=emplace_back && a S(int) [H:\dev\test\0011] > g++ foo.cpp -D ADD=push_back && a S(void*) [H:\dev\test\0011] > _
Addendum: as pointed out by Brian Bi in his answer, another difference that can lead to different behavior is that a push_back
call involves an implicit conversion to T
, which disregards explicit
constructors and conversion operators, while emplace_back
uses direct initialization, which does consider also explicit
constructors and conversion operators.
The emplace
versions don't create an object of the desired type at all under exception circumstances. This can lead to a bug.
Consider the following example, which uses std::vector
for simplicity (assume uptr
behaves like std::unique_ptr
, except the constructor is not explicit):
std::vector<uptr<T>> vec;
vec.push_back(new T());
It is exception-safe. A temporary uptr<T>
is created to pass to push_back
, which is moved into the vector. If reallocation of the vector fails, the allocated T
is still owned by a smart pointer which correctly deletes it.
Compare to:
std::vector<uptr<T>> vec;
vec.emplace_back(new T());
emplace_back
is not allowed to create a temporary object. The ptr
will be created once, in-place in the vector. If reallocation fails, there is no location for in-place creation, and no smart pointer will ever be created. The T
will be leaked.
Of course, the best alternative is:
std::vector<std::unique_ptr<T>> vec;
vec.push_back(make_unique<T>());
which is equivalent to the first, but makes the smart pointer creation explicit.
If you don't have crazy side-effects in copy constructor of the objects that you hold in your vector, then no.
emplace_back
was introduced to optimise-out unnecessary copying and moving.
Suppose a user-defined class could be initialized from braced-initializer. e.g.
struct S {
int value;
};
then
std::vector<S> v;
v.push_back({0}); // fine
v.emplace_back({0}); // template type deduction fails
std::vector::emplace_back
is template function but std::vector::push_back
is non-template function. With a braced-initializer std::vector::emplace_back
would fail because template argument deduction fails.
Non-deduced contexts
6) The parameter P, whose A is a braced-init-list, but P is not
std::initializer_list
or a reference to one:
int main() {
std::vector<S>().push_back(0);
std::vector<S>().emplace_back(0);
}
Give struct constructor in emplace_back i.e The above code will be like this
int main() {
std::vector<S>().push_back(0);
std::vector<S>().emplace_back(S(0));
}
来源:https://stackoverflow.com/questions/22080290/are-there-any-cases-where-it-is-incorrect-to-replace-push-back-with-emplace-back