Are there any cases where it is incorrect to replace push_back with emplace_back?

吃可爱长大的小学妹 提交于 2019-12-01 03:31:39

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.

Cheers and hth. - Alf

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:

LIVE

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