in the code below, what it is used to avoid copy, elision or rvalue reference and move constructor ?
std::string get(){return "...";}
void foo(std::string var){}
foo( get() ); //<--- here
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).
Rvalue references enable you to write one version of a function that accepts arbitrary arguments. Then that function can forward them to another function as if the other function had been called directly. Consider the following example that declares four types, W , X , Y , and Z .
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.
Uses of rvalue references: They are used in working with the move constructor and move assignment. cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int'. cannot bind rvalue references of type 'int&&' to lvalue of type 'int'.
std::string get(){
// this is similar to return std::string("..."), which is
// copied/moved into the return value object.
return "...";
}
RVO allows it to construct the temporary string object directly into the return value object of get()
.
foo( get() );
RVO allows it to directly construct the temporary string object (the return value object) directly into the parameter object of foo
.
These are the RVO scenarios allowed. If your compiler cannot apply them, it has to use move constructors (if available) to move the return value into the return value object and the parameter object, respectively. In this case that is not surprising because both temporary objects are or are treated as rvalues anyway. (For the first scenario, no expression corresponds to the created temporary, so the treatment is only for the purpose of selecting what constructor is used for copying/moving the temporary into the return value object).
For other cases, the compiler has to consider things as rvalues even if they are otherwise lvalues
std::string get(){
std::string s = "...";
// this is similar to return move(s)
return s;
}
The spec says when it could potentially apply RVO (or NRVO) to an lvalue by the rules it sets forth, the implementation is required to treat the expressions as rvalues and use move constructors if available, and only if it couldn't find a suitable constructor, it should use the expression as an lvalue. It would be a pity for the programmer to write explicit moves in these cases, when it's clear the programmer would always want a move instead of a copy.
Example:
struct A { A(); A(A&); };
struct B { B(); B(B&&); };
A f() { A a; return a; }
B f() { B b; return b; }
For the first, it takes a
as an rvalue, but cannot find constructors that accept this rvalue (A&
cannot bind to rvalues). Therefor, it then again treats a
as what it is (an lvalue). For the second, it takes b
as a rvalue, and has B(B&&)
take that rvalue and move it. If it would have taken b
as an lvalue (what it is), then the copy initialization would have failed, because B
has no copy constructor implicitly declared.
Note that returning and paramter passing uses the rules of copy initialization, which means
u -> T (where u's type is different from T) =>
T rvalue_tmp = u;
T target(rvalue_tmp);
t -> T (where t's type is T) =>
T target = t;
Hence, in the example where we return a "..."
, we first create an rvalue temporary and then move that into the target. For the case where we return an expression of the type of the return value / paramter, we will directly move / copy the expression into the target.
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