The C++ draft states:
12.8p31 This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):
(...)
- when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move
In other words:
X MakeX() {
return X(); // Copy elided
}
X MakeX() {
const X& x = X(); // Copy not elided
return x;
}
What is the reason for such restriction for references?
Please do not focus on the validity of the following examples, as they are just to exemplify that I fail to see the difference (IMHO) between temporary and a reference.
On one hand by introduction of reference we allowed other peers to alias the same object, while the caller of MakeX()
expects it to be safe and clean.
class Y {
public:
Y(const X& x) : _xRef(x) {}
private:
const X& _xRef;
};
X MakeX() {
const X& x = X();
Y y{x};
StaticStuff::send(y);
return x; // Oops, I promised to return a clean,
// new object, but in fact it might be silently
// changed by someone else.
}
But what about such case (probably it's UB ;)):
class Y {
public:
Y(X* x) : _xPtr(x) {}
private:
X* _xRef;
};
X MakeX() {
X x;
Y y{&x}; // I'm referencing a local object but I know it will be
// copy elided so present in the outer stack frame.
StaticStuff::send(y);
return x; // Copy elided?
}
Copy elision is an optimization implemented by most compilers to prevent extra (potentially expensive) copies in certain situations. It makes returning by value or pass-by-value feasible in practice (restrictions apply).
C++ Copy Elision Purpose of copy elision Copy elision (sometimes called return value optimization) is an optimization whereby, under certain specific circumstances, a compiler is permitted to avoid the copy or move even though the standard says that it must happen.
In C++20, the only copy allowed in the example is the one at line 3 (actually, x is implicitly moved from). Copy elision (NRVO) is allowed there and is routinely performed by most compilers, but is still non-guaranteed, and the widget class cannot be non-copyable non-movable.
Temporary materializationA prvalue of any complete type T can be converted to an xvalue of the same type T . This conversion initializes a temporary object of type T from the prvalue by evaluating the prvalue with the temporary object as its result object, and produces an xvalue denoting the temporary object.
You never know that a copy will be elided. Copy elision is never mandatory.
Therefore, either both cases are UB, or none. It depends on what StaticStuff:send
does with the object you pass in. If it retains any poitner/reference to y._xRef
or *y._xPtr
, then dereferencing that pointer/reference after MakeX()
has returned will indeed cause UB, as the original object x
was one with automatic storage duration inside MakeX()
and its lifetime has now ended.
It's possible that the result of this UB will be "everything works just fine" because copy ellision did take place and the pointer/reference refers to the instance in the "outer stack frame." However, this is pure coincidence and it's still UB.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With