简体   繁体   中英

Guaranteed copy elision in C++17 and emplace_back(…)

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?

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM