Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Initializing a std::string with function return value, is there a copy?

Tags:

c++

c++11

swap

I have the following code, I am using GCC and C++11:

std::string system_call(const char *cmd){
   std::string a;
   ...
   return a;
}

std::string st = system_call("whatever code");

Is there an implicit copy there? I am calling this function many times, and I guess it is doing a copy from the return value of the system_call to the variable st, and later on releasing the temporary r-value.

Is there any way I can avoid the copy? Using st.swap(system_call()) throws and error in the compiler:

error: no matching function for call to 'std::basic_string::swap(std::string)'

My questions are:

  1. If there is a problem or not
  2. How to avoid it, if there is one

Thanks

EDIT: Found a way to make the explicit swap work. But the answers where right, there is no benefit since the compiler was already replacing st with the return value, without any copy.

system_call("whatever").swap(st);
like image 794
DarkZeros Avatar asked Nov 30 '22 16:11

DarkZeros


2 Answers

In general, if the std::string is constructed in the call and then returned most modern compilers with apply the return value optimization (a special case of copy elision). Your case in particular is the Named RVO (thanks @NathanOliver for pointing this out), if you want to look up the precise rules. From C++17 on this optimization is guaranteed through the standard.

Whether or not the optimization is applied is hard to tell, however if it is not, then in C++11 and above it is very likely that the std::string object in the scope of the function will be moved from and that the return value will be moved to. You could theoretically call std::move on the return value, but thereby you would also prevent any RVO from happening. While move may be efficient, RVO typically yields faster code, because nothing is moved or copied at all, but the object is constructed in place, so I would advise against doing it, and in favor of relying on your compiler.

like image 132
midor Avatar answered Dec 04 '22 02:12

midor


Elision is the permission the standard gives to compilers to make multiple values share existence, when they seem to be different values in the code.

std::string system_call(const char *cmd){
  std::string a;
  ...
  return a; // all return paths return `a` directly
}

std::string st = system_call("whatever code");

In the above case, elision means that a, the return value of system_call and st are all the same object.

Modern compilers elide unless you give them a pathological flag to say "don't elide", whenever the standard and the code permits it. "What if it doesn't elide when possible" is like asking what if the compiler implements integer addition as a looped increment.

Both are permitted by the standard, neither are reasonable to expect.

When elision fails (because your code makes it impossible), it falls back on move semantics in C++11. For std::string that means no memory allocation occurs on the move; in the case of a small string optimization some small number of characters may be copied.

Elision can fail if your code could return a given named variable along one path, and a different one or a temporary along another path. Or if you return a function parameter.

Elision is permitted if your return statement is return named_variable; or return some_temporary_object;, where the returned object matches the type of the function return. It is also permitted when you do some_type bob = some_temporary;, where some_temporary is a temporary object of type some_type. Similarly you can elide into a function argument (but not out of it).

There is no way to "guarantee" elision.

C++17 makes almost all of the elide-from-temporary cases above mandatory.

For elision to work in C++14 and before, there must be a copy or move constructor. When elision occurs, it is not called, but it must exist. In C++17 when temporaries are "force" elided, no copy or move constructor need exist: the temporary is not a distinct object, but rather a clause that represents "how to construct an object", which is only done later.

like image 39
Yakk - Adam Nevraumont Avatar answered Dec 04 '22 04:12

Yakk - Adam Nevraumont