Avoiding copy-building of return value

I'm new to C ++, and I ran into a problem recently returning a link to a local variable. I solved this by changing the return value from std::string& to std::string . However, in my opinion, this can be very inefficient. Consider the following code:

 string hello() { string result = "hello"; return result; } int main() { string greeting = hello(); } 

As far as I understand what happens:

  • hello() .
  • The local variable result to "hello" .
  • The result value is copied to the greeting variable.

This is probably not so important for std::string , but it can definitely become expensive if you have, for example, a hash table with hundreds of records.

How to avoid copy-creating a return temporary value and instead return a copy of a pointer to an object (essentially a copy of a local variable)?


Sidenote: I heard that the compiler sometimes performs optimization of the return value to avoid calling the copy constructor, but I think it's better not to rely on the optimization compiler to execute the code efficiently.)

+7
c ++ reference return return-value
source share
3 answers

The description in your question is pretty correct. But it is important to understand that this is the behavior of an abstract C ++ machine. In fact, the canonical description of the abstract behavior of return is even less optimal

  • result copied to an unnamed intermediate temporary object of type std::string . This temporary position is retained after the function returns.
  • This nameless intermediate temporary object is then copied to greeting after the function returns.

Most compilers have always been smart enough to exclude an intermediate period of time in full compliance with the rules of classical copying. But even without this intermediate temporal behavior, behavior has always been considered grossly suboptimal. That is why compilers have been given greater freedom to provide them with optimization opportunities in contexts with the opposite value. It was originally Return Value Optimization (RVO). Named return value optimization (NRVO) was later added. And finally, in C ++ 11, moving semantics has become an additional way to optimize return behavior in such cases.

Note that in NRVO in your example, initializing the result with "hello" actually puts "hello" directly in greeting from the very beginning.

So, in modern C ++, the best advice is to leave it as it is and not to avoid it. Return it by value. (And prefer to use immediate initialization at the point of declaration when you can, instead of choosing the default initialization followed by the assignment.)

First, the capabilities of the RVO / NRVO compiler can (and will) eliminate copying. In any self-respecting compiler, RVO / NRVO is not something obscure or secondary. This is something that compiler authors are actively seeking to implement and implement correctly.

Secondly, there is always semantics as a fallback if RVO / NRVO somehow fails or is not applicable. Moving is naturally applicable in contexts with return by value, and it is much cheaper than full-scale copying for non-trivial objects. And std::string is a movable type.

+9
source share

I disagree with the sentence "I believe that it is better not to rely on optimizing the compiler to make your code efficient." This is basically a compiler for the whole job. Your task is to write clear, correct, and supported source code. For every performance issue that I have ever had to fix, I had to fix a hundred or more problems caused by the fact that the developer tried to be smart, and not to do something simple, correct and maintained.

Let's look at some of the things you could do to try to β€œhelp” the compiler and see how they affect the maintainability of the source code.

  • You can return the data via the link

For example:

 void hello(std::string& outString) 

Returning data using a link makes the code on the call node hard to read. It is almost impossible to say which function causes the mutate state as a side effect and which are not. Even if you are really careful with the constant qualifying links that are difficult to read on the call site. Consider the following example:

 void hello(std::string& outString); //<-This one could modify outString void out(const std::string& toWrite); //<-This one definitely doesn't. . . . std::string myString; hello(myString); //<-This one maybe mutates myString - hard to tell. out(myString); //<-This one certainly doesn't, but it looks identical to the one above 

Even greeting greetings is unclear. Did he change outString, or did the author just messy and forget that const matches the link? Functional style code is easier to read, understand, and complicate in order to break accidentally.

Avoid

  • You can return a pointer to an object, rather than returning an object.

Returning the pointer to the object makes it difficult to verify the correctness of the code. If you are not using unique_ptr, you must trust that someone using your method is solid and will definitely delete the pointer when it is done with it, but that is not very RAII . std :: string is already a RAII wrapper type for char *, which abstracts the life problems associated with returning a pointer. Returning the pointer to std :: string simply repeats the problems that were designed to solve std :: string. Relying on a person to be diligent and carefully read the documentation for your function and know when to delete the pointer and when not to delete the pointer is unlikely to have a positive result.

Avoid

  • This leads us to move the constructors.

The move designer simply transfers ownership of the data specified in the β€œresult” to the final destination. Subsequently, access to the "result" object is invalid, but it does not matter - your method has ended, and the "result" object has gone out of scope. No copy, just transferring ownership of the pointer with clear semantics.

Typically, the compiler will call the move constructor for you. If you are really paranoid (or know that the compiler will not help you), you can use std :: move .

Do it if at all possible.

Finally, modern compilers are astounding. With the modern C ++ compiler, in 99% of cases, the compiler is going to do some kind of optimization to eliminate the copy. The other 1% of the time will probably not matter for performance. In special cases, the compiler can rewrite a method like std :: string GetString (); void GetString (std :: string & outVar); automatically. The code is still easy to read, but in the final build you get all the real or imaginary advantages in speed of returning by reference. Do not sacrifice readability and maintainability for performance unless you have specific knowledge that the solution does not meet your business requirements.

+4
source share

There are many ways to achieve this:

1) Return some data from the link

 void SomeFunc(std::string& sResult) { sResult = "Hello world!"; } 

2) Return the pointer to the object

 CSomeHugeClass* SomeFunc() { CSomeHugeClass* pPtr = new CSomeHugeClass(); //... return(pPtr); } 

3) C ++ 11 could use the move constructor in such cases. See this this and this for more information.

+3
source share

All Articles