I'm just comparing the performance of passing a string to a function. The benchmark results are interesting.
Here's my code:
void add(std::string msg)
{
msg += "world";
}
void addRvalue(std::string&& msg)
{
msg += "world";
}
void addRef(std::string& msg)
{
msg += "world";
}
void StringCreation() {
add(std::string("hello "));
}
void StringCopy() {
std::string msg("hello ");
add(msg);
}
void StringMove() {
std::string msg("hello ");
add(std::move(msg));
}
void StringRvalue() {
std::string msg("hello ");
addRvalue(std::move(msg));
}
void StringReference() {
std::string msg("hello ");
addRef(msg);
}
StringCreation(), StringRvalue() and StringReference() are equivalent. I'm surprised StringMove() is the least performant - worse than pass by value which involves a copy.
Am I right in thinking that calling StringMove() involves one move constructor followed by a copy constructor when it calls add()? It doesn't just involve one move constructor? I thought move construction was cheap for a string.
I increased the length of the string passed to add() and that did make a difference. Now StringMove() is only 1.1 times slower than StringCreation and StringReference. StringCopy is now the worst, which is what I expected.
Here are the new benchmark results.
So StringMove doesn't involve copying after all - only for small strings.
Pass String by Reference in C++ The C++ reference is a name for a variable that already exists. A reference to a variable can't be altered to refer to the other variable once initialized. Pointers or references can be passed as parameters to functions in C++.
strings are passed by reference. The built in string type is a value type. Variables (also function/method arguments from now on) and struct fields of type string are passed/copied by value.
If the function needs to modify a dynamically allocated (i.e. heap-allocated) string buffer from the caller, you must pass in a pointer to a pointer. In C, function arguments are passed by value. This means that to modify a variable from within a function, you need a pointer to the variable.
Why is it not possible to set a function returning an address while it is possible to set a function that returns a reference. Short answer: You return a pointer by-value and anything returned by-value is (by definition) not an lvalue.
Let's analyze your code and suppose long strings (without applied SSO):
void add(std::string msg) {
msg += "world";
}
void StringCreation() {
add(std::string("hello "));
}
Here, a converting constructor (ConvC) from the string literal is called first to initialize the temporary std::string("hello ")
. This temporary (an rvalue) is then used to initialize the parameter msg
by the move constructor (MC). However, the latter is very likely optimized away by copy elision. Finally, the operator +=
is called. Bottom line: 1x ConvC and 1x +=
.
void StringCopy() {
std::string msg("hello ");
add(msg);
}
Here, the parameter msg
is copy-initialized (by copy constructor - CC) by the lvalue argument msg
. Bottom line: 1x ConvC, 1x CC, and 1x +=
. In case of long strings, this is the slowest version, since copy involves dynamic memory allocations (the only case).
void StringMove() {
std::string msg("hello ");
add(std::move(msg));
}
Why is this slower than StringCreation
? Simply because there is an additional MC involved that initializes the parameter msg
. It cannot be elided, since the object msg
still exist after the call of add
. Just it is moved-from. Bottom line: 1x ConvC, 1x MC, 1x +=
.
void addRef(std::string& msg) {
msg += "world";
}
void StringReference() {
std::string msg("hello ");
addRef(msg);
}
Here, the operator +=
is applied to the referenced object, so there is no reason for any copy/move. Bottom line: 1x ConvC, 1x +=
. Same time as for StringCreation
.
void addRvalue(std::string&& msg) {
msg += "world";
}
void StringRvalue() {
std::string msg("hello ");
addRvalue(std::move(msg));
}
With Clang, the time is same as for StringReference
. With GCC, the time is same as for StringMove
. In fact, I don't have an explanation for this behavior for now. (It seems to me that GCC is creating some additional temporary initialized by MC. However, I don't know why.)
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