问题
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