Using make_shared with emplace_back and push_back - any difference?

1.4k Views Asked by At
some_vector.push_back(make_shared<ClassName>());
some_vector.emplace_back(make_shared<ClassName>());

I want to check that my understanding is correct that for make_shared and in general for all other functions that returns an object those two calls are identical. Here make_shared will create a new shared_ptr, and then this pointer will be moved into the container both in push_back and emplace_back. Is this correct, or will there be some difference?

4

There are 4 best solutions below

0
Yakk - Adam Nevraumont On BEST ANSWER

vector<T>::push_back has a T&& overload, which does the same as the vector<T>::emplace_back T&& version.

The difference is that emplace_back will perfect-forward any set of arguments to the T's constructor, while push_back only takes T&& or T const&. When you actually pass a T&& or T const& the standard specification of their behaviour is the same.

11
Secundi On

I want to add a small detail to Yakk's answer.

The forwarding of arguments for the emplace_back-case can introduce horrible bugs in doubt - even for vectors of shared pointers - if not used with special care, see for instance

#include <vector>
#include <memory>

struct SimpleStruct {};

auto main() -> int
{
    std::vector<std::shared_ptr<SimpleStruct>> v;
    SimpleStruct a;
    v.emplace_back(std::addressof(a)); // compiles, UB
    v.push_back(std::addressof(a)); // fails to compile
}

Yes, that's a kind of an extreme example since code like this should always be used with special care or questioned in general, but it emphasizes, that one should only refer to emplace_back if one hasn't the to copy object already at hands and its only purpose is to be added to the vector, and refer to push_back for all common copy/move-construction cases. It would be nice if the language/standard library could force that from scratch for emplace_back, i.e. only accepting the custom non-copy/move constructors in order to have this clear separation but even if it's possible in an acceptable way, it would be in conflict with many template-context scenarios (fast-forwarding) and the error-prone usage is still possible, although a bit more explicit.

According to my example from above, code refactorization is an important point here in doubt. Simply imagine that the previous code used raw pointers, i.e. the actual underlying bug was already persistent there and hidden by emplace_back -usage. It would also had been hidden by push_back -usage there but not as soon as you update your code to the shared pointer way.

Even if it's not relevant for your particular specific use-case, I think it's worth to be mentioned here since one should be totally confident about the underlying differences between both methods.

Thanks to Human-Compiler in the comments for mentioning my used previous wrong terminology here.

2
Vikas Awadhiya On

To understand this problem let's first consider what would be the result of calling std::make_shared<class_type>(),
It returns temporary object which means Xvalue an eXpiring value whose resources can be reused. Now let's see both cases,

some_vector.push_back(make_shared<ClassName>());

std::vector have two overload of push_back and one of them accept rvalue reference that is
constexpr void push_back( T&& value );
It means value is moved into new element, but how? rvalue overload of push_back will move construct new value by invoking shared_ptr( shared_ptr&& r ) noexcept; and ownership of r will be taken and r become empty.

some_vector.emplace_back(make_shared<ClassName>());

In emplace_back( Args&&... args ) element is constructed through std::allocator_traits::construct by perfect forwarding args.. through std::forward<Args>(args)..., It means rvalue will perfect forward and cause same move constructor shared_ptr( shared_ptr&& r ) noexcept; to be invoked.

Conclusion is, both push_back and emplace_back have same effect.

But what is explained above doesn't happen because compiler comes into the picture and what it does, it perform optimization, It means rather than creating temporary objects and moving them into other objects, it directly creates objects in place.

Again result is same in both cases.

Below, supporting code for compiler optimization theory is included and as you can see output only prints one constructor call.

#include <iostream>

using std::cout;
using std::endl;

class Object{

public:
    explicit Object(int );

    Object(const Object& );
    Object(Object&& );
};

Object::Object(int ){
    cout<< __PRETTY_FUNCTION__<< endl;
}

Object::Object(const Object& ){
    cout<< __PRETTY_FUNCTION__<< endl;
}

Object::Object(Object&& ){
    cout<< __PRETTY_FUNCTION__<< endl;
}

int main(){

    [[maybe_unused]] Object obj(Object(1));
}

Output:
Object::Object(int)

5
jiadong On
  1. some_vector.push_back(make_shared<ClassName>()); rvalue reference is passed to the function, the push_back simply calls emplace_back.

    void push_back(value_type&& __x)
    { emplace_back(std::move(__x)); }