3
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?

1201ProgramAlarm
  • 32,384
  • 7
  • 42
  • 56
Johy
  • 263
  • 2
  • 10
  • 1
    For clarity, can you add the declaration of `some_vector` to the question? – 1201ProgramAlarm Mar 04 '21 at 20:19
  • @1201ProgramAlarm hm, are there some options of declaring it that will make a difference? – Johy Mar 06 '21 at 14:03
  • We have to assume from context that it is a `std::vector>`. It would be better to be explicit in what the declaration is, which would also make it easier for someone else to find your question when searching. – 1201ProgramAlarm Mar 06 '21 at 19:19
  • @1201ProgramAlarm but what are other options of declaring it for this case? I just can't think of something else than std::vector>, so curious to know if there is something else – Johy Mar 07 '21 at 00:40
  • @Johy check [this](https://stackoverflow.com/questions/45345175/avoiding-extra-move-in-make-unique-make-shared-emplace-etc-for-structures-that-u) out – C.M. Mar 08 '21 at 03:51

4 Answers4

7

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
1

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.

Secundi
  • 1,150
  • 1
  • 4
  • 12
  • 1
    There is no implicit conversion going on with `emplace_back`. Any `emplace` function tells the container to call the underlying constructor directly. Since `shared_ptr` is *explicitly* constructible from raw pointers, it calls that constructor exactly as asked by the person who wrote the code. This is no different than if someone were to make `shared_ptr{std::addressof(a)}` manually. Yes it's a bug -- but it's not a "gotcha" from implicit construction; it's just bad code. – Human-Compiler Mar 10 '21 at 14:03
  • Maybe my wording was not precise here. With implicit conversion, I did not meant the class-level conversion terminology (operator-usage) but the effective operation observable. Common according operator implementations are not something special here, they also have to construct the target object somehow (i.e. indirection). Further on, the issues here can happen even for not that obviously bad code, as Scott Meyers for instance showed multiple times within his books. – Secundi Mar 10 '21 at 14:29
  • I'm going to correct my used terminology within my answer but "There is no implicit conversion going on with emplace_back" is simply wrong that absolute. Due to fitting constructor look up, implicit conversions of the provided arguments are possible in general, for instance between const char pointers and std::string. – Secundi Mar 10 '21 at 14:58
  • " it's just bad code." Yes but the origin of the bug might have been a refactorization from raw to shared_ptr usage and you are simply not able to detect this bug that simple then. Hence, emplace_back usage almost always has to be used with special care. – Secundi Mar 10 '21 at 15:17
  • 1
    I think there's more to it than just "bad code". It's not immediately obvious that explicit constructors are allowed; you don't see the type name as you'd normally see when doing an explicit construction. Even `return {..}` doesn't allow explicit ctors IIRC. – dyp Mar 10 '21 at 15:18
  • Human-Compiler - With my refactorization example itself, I meant the exposure of the bug (what would have been possible if the emplace_back usage was done according these "guidelines"), admitting, that the bug was there before already. – Secundi Mar 10 '21 at 15:29
  • 1
    @Secundi I understand your argument; I'm just not sure I agree with it. There are very few cases where such an accident can occur, and all of them -- even if they came from refactoring -- are no different than if a user just called the wrong function or wrong constructor anywhere else in the codebase. Given the context of this question, I don't see why "Make sure to call the correct function" applies as an answer any more than saying "Make sure to call the correct function" would apply on a question of `std::move` vs `std::forward`. – Human-Compiler Mar 10 '21 at 15:45
  • "are no different than if a user just called the wrong function or wrong constructor anywhere else in the codebase" Maybe it just really arises for the accumulation within containers for instance. Calling the wrong constructor "standalone" is often far more explicit at least for the non-template case, than using the wrong arguments for the indirection way via a container. – Secundi Mar 10 '21 at 15:56
  • A further example: Wide distributed dependencies: Again refactorization. Expanding a simple POD type to a (non-trivial) abstraction class that takes this POD-type as possible construction argument (typical for integer abstraction). In order to be sure, your code is still fine, you have to check all `emplace_back` uses manually while the compiler would have generated errors for the `push_back` case for you. – Secundi Mar 11 '21 at 14:20
  • The point I want to emphasize is a simple design principle: Use and refer to the schemes/functions/data types that are as close as possible to your actual "abstract" semantics and purposes. `push_back` is definitively more explicit about its underlying behavior than `emplace_back` for many cases. At latest within a template context with `emplace_back`, you always open up a an almost fully separated second layer of possible error-prone usage, it's surely a second entity in addition to your class itself. – Secundi Mar 11 '21 at 14:50
  • And the sensitivity to bugs is higher at all from at least two case-views: the refactorization case and possible ambiguities in terms of implicit conversion schemes from the possibly chosen constructors, even the ones marked with explicit...The latter is surely an aspect always even without the presence of `emplace_back` but since even explicit constructors are affected, the possible unnecessary confusion can likely be multiplied. – Secundi Mar 11 '21 at 14:52
-1

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)

Vikas Awadhiya
  • 290
  • 1
  • 8
  • 2
    `make_shared` does not return an X-value, it returns a PR-value because it's a **Pure RValue**. XValues are the result of explicit casts to rvalues (e.g. `std::move`/`std::forward`/`static_cast`) – Human-Compiler Mar 10 '21 at 15:09
  • Also your code example is not illustrative of what `push_back` does. There is a difference between eliding a constructor call into another constructor like this, and forwarding it through a function via an rvalue reference. The language does not guarantee elision in that case -- and such an optimization would be a compiler-nicety but not a guarantee. It depends on how the input is called into `push_back`/`emplace_back`. As a PR-value, sure the compiler might be nice. As an xvalue from a moved instance that was used before being inserted? Probably not -- in either of the cases. – Human-Compiler Mar 10 '21 at 15:15
-1
  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)); }
    
jiadong
  • 135
  • 7
  • There is no guarantee in the C++ standard that `push_back` calls `emplace_back`. It's a valid implementation, but this is not a fact or a requirement. `libc++`, for example, [does not](https://github.com/llvm-mirror/libcxx/blob/78d6a7767ed57b50122a161b91f59f19c9bd0d19/include/vector#L1630-L1641). The only requirements for `push_back` are listed in the [sequence container requirements](https://timsong-cpp.github.io/cppwp/n4659/container.requirements#sequence.reqmts-14) which states that for `push_back(rv)` it "Appends a copy of rv.". – Human-Compiler Mar 10 '21 at 19:52
  • @Human-Compiler The example you showed is not the standard library. Please look into stl_vector.h. I am using MinGW9.2.0. It is located in MinGW9.2.0/include/c++/9.2.0/bits/stl_vector.h In STL, two versions of `push_back` are provided, one accepts lvalue reference. and the other one accepts rvalue reference. The latter one just calls `emplace_back()`. – jiadong Mar 10 '21 at 20:15
  • void push_back(const value_type& __x) { if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage) { _GLIBCXX_ASAN_ANNOTATE_GROW(1); _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish, __x); ++this->_M_impl._M_finish; _GLIBCXX_ASAN_ANNOTATE_GREW(1); } else _M_realloc_insert(end(), __x); } #if __cplusplus >= 201103L void push_back(value_type&& __x) { emplace_back(std::move(__x)); } – jiadong Mar 10 '21 at 20:16
  • 1
    You appear to have some pretty major misunderstandings about what "the standard library" is. Each compiler/vendor provides a different implementation of "the standard library"; what you are describing is specifically MinGW's implementation which comes from a modified `libstdc++` (GCC's version). I just linked you `libc++`'s which is used by `clang`, and I also linked for you the C++ standard document that states the requirements for a standard library implementation – Human-Compiler Mar 10 '21 at 20:24
  • @Human-Compiler Thanks. I got it. So it is only valid for MinGW implementation. – jiadong Mar 10 '21 at 20:31