Why should I or shouldn't I create all my functions and members functions to take a rvalue
and leave out versions that take a lvalue
? You can always forward lvalue
to rvalue, right? I can even have const rvalue
, so why is this a bad idea, or a good idea?
What I mean in code is below. The "&&" rvalue
reference allows the users to use temporaries and can still use lvalue
by a simple forward. So with this in mind, why should I provide print_string(string& str)
(lvalue
reference) of any function in c++11 (besides const
reference, because rvalue
there is fine)?
#include <iostream>
#include <string>
#include <utility>
using namespace std;
void print_string(string&& str)
{
str += "!!!";
cout << str << endl;
}
void print_string2(string& str)
{
str += "???";
cout << str << endl;
}
int main(int argc, char* argv[])
{
print_string("hi there"); // works
print_string(string("hi again")); // works
string lvalue("lvalue");
print_string(forward<string>(lvalue)); // works as expected
//print_string(lvalue); // compile error
//print_string2("hi there"); // comile error
//print_string2(string("hi again")); // compile error
print_string2(lvalue);
char a;
cin >> a;
}
hi there!!!
hi again!!!
lvalue!!!
lvalue!!!???
void print_string(string&& str)
provides a more flexible use case than void print_string2(string& str)
so why shouldn't we use rvalue arguments all the time?
Rvalue References As Return Types So it is only really applicable if you're returning something like member variables or reference parameters and you're moving them out of place. There is only one function that moves a reference parameter out: std::move .
Typically rvalues are temporary objects that exist on the stack as the result of a function call or other operation. Returning a value from a function will turn that value into an rvalue.
rvalue of User Defined Data type can be modified. But it can be modified in same expression using its own member functions only.
Reference is a type that doesn't have null value. It's true for any kind of references - rvalues or lvalues. If you want to have null values - use pointers.
As with many features of C++, there is a convention of how to use lvalue and rvalue references.
Another example for conventions are the operators, especially ==
and !=
. You could overload them to return double
s, and copy files for the comparison. But per convention, they return bool
and only compare the left and right operand without modifications.
The convention for rvalue references is that they refer to objects whose resources can be "stolen":
Rvalue references have been introduced to support move semantics, that is, transfer of ownership of resources. Move semantics conventionally implies that an object bound to an rvalue reference can be moved from. Moved-from objects conventionally are assumed to be in a valid, but unknown state. For example:
// returns a string that equals i appended to p
string join(string&& p, int i); // string&& means: I do something to p. Or not.
// append i to p
void append(string& p, int i);
string my_string = "hello, world."; // length is 13
append(my_string, 42); // I know what state my_string is specified to be in now
my_string[12] = "!"; // guaranteed to work
string result = join( std::move(my_string), 42 );
// what state is my_string now in?
char c = my_string[1]; // is this still guaranteed to work?
string const cresult = join("hello, number ", 5);
// fine, I don't care what state the temporary is in -- it's already destroyed
One could think of definitions like:
string join(string&& p, int i)
{ return std::move(p) + std::to_string(i); }
void append(string& p, int i)
{ p += std::to_string(i); }
This definition of join
really does steal the resource from p
, and the Standard really doesn't specify the state of p
after the operation std::move(p) + std::to_int(i)
(which creates a temporary that steals the resource from p
).
For this simple example, you could still use
string result = "hello ";
result = join(std::move(result), 42);
to "replace" append
. But moves can also be expensive (they always also copy something), and multiple lvalue reference parameters cannot easily be replaced by this technique.
N.B. in my opinion, you should indicate in the name of a function when it takes an lvalue reference and modifies the argument. I certainly don't expect a function named print_string2
to modify the string I pass in.
IMHO, it's the convention that the moved-from object is in a valid, but unspecified state that should keep you from using rvalue references everywhere.
Additionally, there can be a slight performance impact when using too many moves as opposed to in-place manipulation à la join
.
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