Copy elision when creating object inside emplace()

旧时模样 提交于 2021-02-06 15:15:37

问题


I see a lot of code at work where people use emplace and emplace_back with a temporary object, like this:

struct A {
    A::A(int, int);
};

vector<A> v;
vector<A>.emplace_back(A(1, 2));

I know that the whole point of emplace_back is to be able to pass the parameters directly, like this:

v.emplace_back(1, 2);

But unfortunately this is not clear to a few people. But let's not dwell on that....

My question is: is the compiler able to optimize this and skip the create and copy? Or should I really try to fix these occurrences?

For your reference... we're working with C++14.


回答1:


My question is: is the compiler able to optimize this and skip the create and copy? Or should I really try to fix these occurrences?

It can't avoid a copy, in the general case. Since emplace_back accepts by forwarding references, it must create temporaries from a pure standardese perspective. Those references must bind to objects, after all.

Copy elision is a set of rules that allows a copy(or move) constructor to be avoided, and a copy elided, even if the constructor and corresponding destructor have side-effects. It applies in only specific circumstances. And passing arguments by reference is not one of those. So for non-trivial types, where the object copies can't be inlined by the as-if rule, the compiler's hands are bound if it aims to be standard conformant.




回答2:


is the compiler able to optimize this and skip the create and copy?

There is not necessarily a copy involved. If a move constructor is available, there will be a move. This cannot be optimized away, as the direct initialization case will just call the init constructor, while in the other case, the move constructor will be called additionally (including its side-effects).

Therefore, if possible, you should refactor those codes.




回答3:


The easy answer is no; elision doesn't work with perfect forwarding. But this is c++ so the answer is actually yes.

It requires a touch of boilerplate:

struct A {
  A(int, int){std::cout << "A(int,int)\n"; }
  A(A&&){std::cout<<"A(A&&)\n";}
};

template<class F>
struct maker_t {
  F f;
  template<class T>
  operator T()&&{ return f(); }
};

template<class F>
maker_t<std::decay_t<F>> maker( F&& f ) { return {std::forward<F>(f)}; }

vector<A> v;
v.emplace_back(maker([]{ return A(1,2); }));

live example.

Output is one call to A(int,int). No move occurs. In c++17 the making doesn't even require that a move constructor exist (but the vector does, as it thinks it may have to move the elements in an already allocated buffer). In c++14 the moves are simply elided.




回答4:


I just want to add

There is a great 5 minutes lightning talk about copy elision and RVO from Jon Kalb https://youtu.be/fSB57PiXpRw

Also, you might get different results using different compilers gcc, clang or icc

See compiler explorer, try different compilers and settings and see for yourself

https://godbolt.org/g/Yjo9qA



来源:https://stackoverflow.com/questions/50740699/copy-elision-when-creating-object-inside-emplace

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!