Lvalues, Rvalues, And Moving¶
Return Object Problem: Reference To Stack-Based Object¶
Whole class of problems: lifetime of returned objects
const std::string& f() {
std::string s{"blah"};
return s;
}
|
warning: reference to local variable ‘s’ returned
std::string s{"blah"};
^
|
const std::string& f() {
return "blah";
}
|
warning: returning reference to temporary
return "blah";
^
|
C string converted to
std::string
to match return typeObject’s home is on the stack
Returning reference to it
⟶ “undefined behavior”
Fortunately compilers can detect and warn
Return Object Problem: Solution: Copy¶
Solution: return by copy
std::string f() {
return "blah";
}
Before return, construct temporary from
"blah"
During return, copy-construct receiver object
After return (during stack frame cleanup), destroy temporary
⟶ Performance
std::vector<int> f() {
std::vector<int> v;
int i=100000;
while (i--)
v.push_back(i);
return v;
}
Move Semantics: Wish List¶
Copy is expensive ⟶ want cheap move instead
What if the compiler could help out?
Compiler should do that for me (he knows that the local variable is not used anymore after return)
Determine automatically that an object cannot be used anymore
If so, call a special kind of constructor (move constructor) that takes ownership
Otherwise, insert copy constructor as usual
Theory: Lvalues¶
Basically …
Anything that can be assigned to
Anything that has an address
Anything that can be dereferenced
int a = 42;
int b = 43;
a = b;
b = a;
a = a * b;
char* buffer = new char[1];
*buffer = 'a'; // <--- some expressions are lvalues
buffer[0] = 'a'; // <--- some expressions are lvalues
a * b = 42; // <--- some are not (error)
Theory: Rvalues¶
Basically …
Anything that is not an lvalue is an rvalue
Literal constants
42 = 666; // <--- error
This is ok though (because … ?)
std::string("blah") = "blech";
Sadly, this is ok too (because … ?)
std::string f(); f() = "blech"; // <--- ok
Scott Meyers (Effective C++) says you shoud declare copy-returns as
const
to make it consistent 🐷const std::string f(); f() = "blech"; // <--- error
This is intended
std::string& f(); f() = "blech"; // <--- ok
Results of built-in operators that aren’t lvalues
int a = 1, b = 2; a + b = 666; // <--- error
Lvalue References (The Good Old Ones)¶
Non-const lvalue references can only bind to lvalues
int a; int& b = a; // <--- ok int& c = a * 2; // <--- error: cannot bind to rvalue
Const lvalue references can bind to lvalue or rvalues
const int& r1 = a * 2; // <--- ok: bind to rvalue const std::string& r2 = f(); // <--- ok: bind to rvalue
⟶ Non-trivial: compiler has to set aside storage to outlive the reference
Rvalue References: So What Is That?¶
Double ampersand
&&
Rvalue references can only bind to rvalues
And can be assigned to!
int&& rvr = 42; // <--- bound to an rvalue (an int literal) rvr = 7; // <--- assigned to!
Cannot bind to lvalues
int a = 666; int&& rvr = a; // <--- error: cannot bind rvalue reference to lvalue
But rvalue references just don’t make sense as standalone variables
⟶ Preferred use as function parameters for overload resolution
Rvalue References As Function Parameters¶
Semantics just the same as with standalone variables
⟶ Rvalue references can only bind to rvalues
void f(int&& param);
int a;
f(a); // <--- error: cannot bind rvalue reference to lvalue
f(a*2); // <--- ok: rvalue
Use Of Rvalue References Parameter Inside A Function: Is An Lvalue¶
Inside a function, and rvalue type parameter is an lvalue
It has a name, after all! And an address!
void f(int&& param); void g(int&& param) { f(param); // <--- error: cannot bind rvalue reference to lvalue }
Enter
std::move
#include <utility> void f(int&& param); void g(int&& param) { f(std::move(param)); }
Converts lvalue to rvalue
⟶ Object copy vs. object move
Enter Move Constructor And Move Assignment¶
Compiler knows that after the return of a local variable that variable cannot be used anymore
⟶ inserts move constructor and/or move assignment operators
class foo
{
public:
foo(foo&&) noexcept;
foo& operator=(foo&&) noexcept;
};
Rules
Move constructor is implicitly defined by compiler as member- (and base-) wise move if there are no user-declared …
Destructor
Copy constructor
Copy assignment operator
Move assignment operator
Move assignment operator is implicitly defined by compiler as member- (and base-) wise move if there are no user-declared …
Destructor
Copy constructor
Copy assignment operator
Move constructor