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

前端 未结 6 1421
不思量自难忘°
不思量自难忘° 2021-01-02 09:44

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 e

相关标签:
6条回答
  • 2021-01-02 10:11

    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.

    0 讨论(0)
  • 2021-01-02 10:16

    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.

    0 讨论(0)
  • 2021-01-02 10:17

    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

    0 讨论(0)
  • 2021-01-02 10:22

    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.

    0 讨论(0)
  • 2021-01-02 10:22

    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.

    0 讨论(0)
  • 2021-01-02 10:33
    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)); 
    }
    
    0 讨论(0)
提交回复
热议问题