Should I Return Std::Strings

best way to return an std::string that local to a function

No. That is not true. Even if mystring has gone out of scope and is destroyed, ret has a copy of mystring as the function MyFunc returns by value.

Should I return std::strings?

Return the string.

I think the better abstraction is worth it. Until you can measure a meaningful performance difference, I'd argue that it's a micro-optimization that only exists in your imagination.

It took many years to get a good string abstraction into C++. I don't believe that Bjarne Stroustroup, so famous for his conservative "only pay for what you use" dictum, would have permitted an obvious performance killer into the language. Higher abstraction is good.

In c++11, does returning a std::string in a function move or copy it?

Your example fall on the so-called Named Return Value Optimization, which is defined in this paragraph of the C++11 standard. So the compiler may elide the copy constructor (or move constructor since C++14). This elision is not mandatory.

In C++11, if the compiler does not perform this elision, the returned string will be copy constructed. The returned object would be moved if it were naming a function parameter, [class.copy]/32 (bold is mine):

When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. [...]

In C++14, this last rule has changed. It also includes the case of automatic variables [class.copy]/32:

When the criteria for elision of a copy/move operation are met, but not for an exception-declaration, and the object to be copied is designated by an lvalue, or when the expression in a return statement is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. [...]

So in your example code, and in C++14, if the compiler does not elide the copy/move construction, the returned string will be move constructed.

Is it safe to return a std::string by value?

This creates a second instance of the string object, but both string objects now point to the same char buffer on the heap.

No, they don't.

std::strings own their contents. When you copy a std::string, you copy its buffer.

I think a lot of people assume that, because the string was returned by value, they need not be concerned about the lifetime of the original string within Foo.

And those people are right. There is no "sharing".

Your return by value is fine and you needn't think more about it.

What is better: return std::string by value or by constant reference?

By value.

I encountered similar code in the wild:

A foo();

std::string const& x = foo().test_by_const_ref();

Boom, x is a dangling reference.

That does not happen with returns by value.

Should std::string be returned by value from a function or by std::string &s as an argument?

Let me micro-optimize your second version of f() and call it g():

#include <cstdio>
#include <string>
using namespace std;

string f(const string& s) {
return s + "some text";
}

void g(const string& s, string &result) {
result.clear();
result += s;
result += "some text";
}

Now, let's compare the return by value approach f() to the "out-parameter" approach g().

Return by value:

int main(int argc, char* argv[]) {

string s(argv[1]);

for (int i=0; i<10; ++i) {

string temp = f(s); // at least 1 memory allocation in each iteration, ouch!

fprintf(stderr, "%s\n", temp.c_str());
}
}

In each iteration, there is a memory allocation. The total number of allocations will be the number of iterations + 1, that is, 11 in this case.

The "out-parameter" approach:

int main(int argc, char* argv[]) {

string s(argv[1]);

string temp; // note that this time, it is outside the loop

for (int i=0; i<10; ++i) {

g(s, temp);

fprintf(stderr, "%s\n", temp.c_str());
}
}

In this case, you get 3 memory allocations (assuming the buffer of temp doesn't need to be re-allocated inside the loop), even if you iterate 1000000 times! That is a significant improvement over the return by value approach.

Returning by value and relying on copy-elision or on move semantics is a good advice, but as the example shows, there are situations in which the out-parameter approach wins (e.g. when you can re-use a buffer).

The danger with out-parameters is that at the call site, it must be obvious, just by looking at the code, that the function is modifying some of its arguments. The name of the function must strongly suggest that it is mutating some of its arguments. Otherwise you get surprising results... :(


If you find this example too twisted, well, it isn't: Think of std::getline()!

And for those who think it is premature optimization: In case of std::getline() it certainly isn't! If you shove the lines of a file into a std::vector and allocate a new string for each line it will be 1.6x slower than the out-paramter approach (with lines of 80 bytes). It sounds crazy as the file IO should be the bottleneck but it isn't, it is the unnecessary memory allocations. For details, see Andrei Alexandrescu: Writing Quick Code in C++, Quickly at around 48 min.


UPDATE:

  1. R. Martinho Fernandes kindly pointed out below in comments that his measurements with
    gcc contradict my results but are in agreement with my claims with
    clang and libc++; see
    GCC
    and
    Clang.

  2. After he pointed out these, I made measurements on Andrei
    Alexandrescu's example. At the moment, I cannot reproduce his
    results; it needs further analysis as to understand what is happening under the
    hood.

Please be patient and give me some time to clear up the inconsistencies.

The take-away of this story is to always measure. I did measure the number of memory allocations mentioned in the answer, that is still OK (at least on my machine).

Is there any point in returning an object (e.g., std::string) by reference when the method has no parameters?

Yes, there is a point, you return by reference if you want to return a reference to some object.

Why would you want to have a reference to some object? Exactly because you need to access it and not a copy of it. Reasons might vary, basic ones are that you do not want to make an extra copy - e.g. the get_name you posted, maybe you want to store it and access it later, and/or because you want to modify it.

Returning a reference is not much different from a passing parameter by reference.

No temporary std::string object is made in x.get_name(). The method returns lvalue reference by value. Since references are usually implemented as pointers, the true return value is a pointer. So a copy of the pointer is made during each call but that is like returning an int - can be done in registers or stack. So it's as cheap as it gets.

Yes, your understanding is correct, although I would say that const T& is used when we want to avoid copy for whatever reasons and T& should only be used when we need to get mutable access to the object - e.g. std::ostream& in operator<< which mutates the stream by printing into it.

BTW, you make an extra copy in your ctor - name parameter is copied into name member. Instead you should move it there like Foo(std::string name):name(std::move(name)){}.

C++: Return std::string reference from stack memory

You are really close to making those functions work:

std::string function()
{
string s = "Faz";
s += "Far";
s += "Boo";
return s;
}

Simply make them return a copy instead of a reference and you're set. This is what you want, a copy of the stack-based string.

It gets better too, because the return value optimization (RVO) will only create the string once and return it, just like if you had created it on the heap and returned a reference to it, all behind the scenes!



Related Topics



Leave a reply



Submit