6

emplace_back(...) was introduced with C++11 to prevent the creation of temporary objects. Now with C++17 pure lvalues are even purer so that they do not lead to the creation of temporaries anymore (see this question for more). Now I still do not fully understand the consequences of these changes, do we still need emplace_back(...) or can we just go back and use push_back(...) again?

Nils
  • 13,319
  • 19
  • 86
  • 108
  • Perhaps you mean pure rvalues? Argument of `push_back` won't be a pure rvalue or even a normal rvalue (without an extra cast). So if you constructing a new value then it would be better to use `emplace_back` – user7860670 Aug 07 '19 at 08:03
  • 3
    emplace_back gives you additional functionality over push_back, it allows you to directly specify arguments which are passed to constructor. – marcinj Aug 07 '19 at 08:03
  • 1
    Copy elision is for return values... – Aconcagua Aug 07 '19 at 08:04
  • @Aconcagua Isn't copy elision for passing parameters by value as well? For return values, it's a specific case named _(named) return value optimization_. – Daniel Langr Aug 07 '19 at 08:13
  • 1
    `push_back` accepts only a single argument. Therefore, if you need to pass multiple arguments to the constructor of an inserted element, then with `push_back`, you need to manually write its type. Such as `v.push_back(std::string(10,'x'));` instead of `v.emplace_back(10,'x');`. – Daniel Langr Aug 07 '19 at 08:17
  • 1
    From the first glance, `emplace_back` seems to be the superior alternative compared to `push_back`. Everything you can do with `push_back` can also be achieved with `emplace_back` and additionally, `emplace_back` allows you to forward arbitrary many arguments to the constructor. However, there is a difference when it comes to implicit vs explicit constructors. This answer explains it quite well: https://stackoverflow.com/a/36919571/6095394 – pschill Aug 07 '19 at 08:46
  • @DanielLangr Copy elision is the more general term and is the way (N)RVO is implemented. The [standard](http://eel.is/c++draft/class.copy.elision) lists the situations when copy elision is allowed, among which function parameters are not (exept for co-routines). For parameters, there are already means to avoid copies (references; I suppose this is the reason for the standard not allowing copy elisions on parameters...), but these are not available for return values. – Aconcagua Aug 07 '19 at 10:14
  • @Aconcagua Not only for return values. See here: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0135r0.html "when a temporary object is used to initialize another object (including the object returned by a function, or the exception object created by a throw-expression)" – Nils Aug 07 '19 at 10:51
  • @Nils You haven't peaked into the link I provided, have you? – Aconcagua Aug 07 '19 at 10:59
  • @Aconcagua As far as I understand it: This is not really copy elision (but unfortunately called so) bc according to C++17 prvalues do not require the creation of a temporary (that can be elided later). So no temporary, no elision! – Nils Aug 07 '19 at 11:05
  • @Aconcagua You're right that the paragraph you linked changed in C++17. In C++11/14, there was an additional case that might apply to initialization of parameters as well. That's what I meant, but this question is about C++17. – Daniel Langr Aug 07 '19 at 13:04

2 Answers2

2

Both push_back and emplace_back member functions create a new object of its value_type T at some place of the pre-allocated buffer. This is accomplished by the vector's allocator, which, by default, uses the placement new mechanism for this construction (placement new is basically just a way of constructing an object at a specified place in memory).

However:

  • emplace_back perfect-forwards its arguments to the constructor of T, thus the constructor that is the best match for these arguments is selected.
  • push_back(T&&) internally uses the move constructor (if it exists and does not throw) to initialize the new element. This call of move constructor cannot be elided and is always used.

Consider the following situation:

std::vector<std::string> v;
v.push_back(std::string("hello"));

The std::string's move constructor is always called here that follows the converting constructor which creates a string object from a string literal. In this case:

v.emplace_back("hello");

there is no move constructor called and the vector's element is initialized by std::string's converting constructor directly.


This does not necessarily mean the push_back is less efficient. Compiler optimizations might eliminate all the additional instructions and finally both cases might produce the exact same assembly code. Just it's not guaranteed.


By the way, if push_back passed arguments by value — void push_back(T param); — then this would be a case for the application of copy elision. Namely, in:

v.push_back(std::string("hello"));

the parameter param would be constructed by a move-constructor from the temporary. This move-construction would be a candidate for copy elision. However, this approach would not at all change anything about the mandatory move-construction for vector's element inside push_back body.

Daniel Langr
  • 22,196
  • 3
  • 50
  • 93
1

You may see here: std::vector::push_back that this method requires either CopyInsertable or MoveInsertable, also it takes either const T& value or T&& value, so I dont see how elision could be of use here.

The new rules of mandatory copy ellision are of use in the following example:

struct Data {
    Data() {}
    Data(const Data&) = delete;
    Data(Data&&) = delete;
};

Data create() {
    return Data{};  // error before c++17   
}

void foo(Data) {}

int main()
{
    Data pf = create();
    foo(Data{});  // error before c++17
}

so, you have a class which does not support copy/move operations. Why, because maybe its too expensive. Above example is a kind of a factory method which always works. With new rules you dont need to worry if compiler will actually use elision - even if your class supports copy/move.

I dont see the new rules will make push_back faster. emplace_back is still more efficient but not because of the copy ellision but because of the fact it creates object in place with forwarding arguments to it.

marcinj
  • 48,511
  • 9
  • 79
  • 100
  • 1
    "emplace_back is still more efficient but not because of the copy ellision but because of the fact it creates object in place with forwarding arguments to it." That is what I do not get: If no temporary is created what is then the difference to creating the object in-place? – Nils Aug 07 '19 at 09:18
  • 1
    @Nils std::vector have some capacity - which means it have more memory allocated than it requires currently, when you emplace_back to it, then std::vector will use `placement new` which constructs object in already allocated memory. With c++11 you are allowed to forward arguments which in the end allows library creators to implement emplace_back. So when you emplace_back - only constructor is being called (assuming capacity is enough) and maybe arguments might get copied/moved. But I wonder if such arguments might take benefit of mandatory elision since c++17. – marcinj Aug 07 '19 at 09:31
  • 1
    If you push_back(MyNonMovableType()) does guaranteed copy elision not apply here? Isn't MyNonMovableType() a prvalue? – Nils Aug 07 '19 at 10:57
  • No, it will ask for a move constructor : https://coliru.stacked-crooked.com/a/39af319eafaaefab – marcinj Aug 07 '19 at 11:02
  • I guess that is because push_back(...) only takes a constant lvalue reference or a rvalue reference. But this works: https://coliru.stacked-crooked.com/a/edf5552c4944e3a8 So ... if we had `void push_back( T value );` it would work as well! – Nils Aug 07 '19 at 11:09
  • Ooooh that will actually make the habit of using const refs to avoid copies obsolete in this cases! – Nils Aug 07 '19 at 11:11
  • 1
    @marcinj You generally cannot use `std::vector` with a value type that does not support both copy/move construction. Even `reserve` member function requires the value type to be _move-insertable_. (That's, e.g., why you cannot have a vector of atomic ints: https://wandbox.org/permlink/Nu3IZWLeInjfZZAg). – Daniel Langr Aug 07 '19 at 13:23