I've already asked on code review and software engineering but the topic didn't fit the site, so I'm asking here hoping this is not opinion-based. I am an "old school" C++ developer (I've stopped at C++ 2003) but now I've read a few books on modern C++ 11/17 and I'm rewriting some libraries of mine.
The first thing I've made is adding move constructor/assignment operator where needed ( = classes that already had destructor + copy constructor and copy assignment). Basically I'm using the rule of five.
Most of my functions are declared like
func(const std::string& s);
Which is the common way to pass a reference avoiding a copy. By the way there is also the new move semantic and there's somethig that I wasn't able to find in my books/online. This code:
void fun(std::string& x) {
x.append(" world");
std::cout << x;
}
int main()
{
std::string s{"Hello "};
fun(s);
}
Can also be written as:
void fun(std::string&& x) {
x.append(" world");
std::cout << x;
}
int main()
{
std::string s{"Hello "};
fun(std::move(s));
//or fun("Hello ");
// or fun(std::string {"Hello" });
}
My question is: when should I declare functions that accept a paramenter that is a rvalue reference?
I understand the usage of && semantic on constructors and assignment operators but not really on functions. In the example above (first function) I have a std::string& x
which cannot be called as fun("Hello ");
of course because I should delcare the type as const std::string& x
. But now the const doesnt allow me to change the string!
Yes, I could use a const cast but I rarely do casts (and if it's the case, they're dynamic casts). The power of the && is that I avoid copies, I don't have to do something like
std::string x = "...";
fun(x); //void fun(std::string& x) {}
and I can assing temporary values that will be moved. Should I declare functions with rvalue references when possible?
I have a library that I'm rewriting with modern C++ 17 and I have functions like:
//only const-ref
Type1 func(const type2& x);
Type3 function(const type4& x);
I am asking if it's worth rewriting all of them as
//const-ref AND rvalue reference
Type1 func(const type2& x);
Type3 function(const type4& x);
Type1 func(type2&& x);
Type3 function(type4&& x);
I don't want to create too many overloads that may be useless but if an user of my library wanted to use the move operation I should create the && param types. Of course I am not doing this for primitive types (int, double, char...) but for containers or classes. What do you suggest?
I am not sure if the latter scenario (with both versions) would be useful or not.
Let me comment on four scenarios in your question and examples.
std::string_view
with pass-by-value is supposed to replace const std::string&
parameters and whenever you can guarantee the necessary preconditions for a safe usage of std::string_view
(lifetime, pointee doesn't change), it's a good candidate to start modernizing your function signatures.const T&
vs. T&&
(where T
is not subject to template type deduction) with known usage scenarios. The void fun
function that appends to a given, modifiable string, will only makes sense as void fun(std::string&&)
if calling code doesn't need the result after the call. In this case, the rvalue-reference signature documents this expectation nicely and is the way to go. But these cases are rather rare in my experience.const T&
vs. T&&
(again, no type deduction) with unknown usage scenarios. A good reference here is std::vector::push_back
, which is overloaded for both rvalue and lvalue references. The push_back operation is assumed to be cheap compared to move-construction a T
, that's why the overload makes sense. When a function is assumed to be more expensive than such a move-construction, passing the argument by value is a simplification that can make sense (see also Item 41 in EMC++).const T&
vs. T&&
when type deduction takes place. Here, use universal references together with std::forward
whenever possible and the parameters can't be const qualified. If they aren't modified in the function body, go with const T&
.You want to use rvalue references only if:
You might retain a copy and you need the extra performance (measure!)
Example for this would be writing a library type (e.g. std::vector
) where performance matters to its users.
You want only temporaries to be passed to your function
Example for this is the move assignment operator: After the assignment, the original objects state will not exist anymore.
Forwarding references (T&&
with T
deduced) fall under the first option.
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