2

Assuming a basic method:

std::string StringTest()
{
    std::string hello_world("Testing...");

    return std::move(hello_world);
}

How should I use the output:

Option A:

auto& string_test_output=StringTest();

Option B:

auto string_test_output=StringTest();

Is StringTest() a temporary value? If so, Option A would not be safe. If it's not a temporary value, I have a feeling that Option B would cause a copy.

I am still getting used to RValue references so returning by value is still very scary as I don't want copying to occur or run into unnecessary copying!

Cassio Neri
  • 19,583
  • 7
  • 46
  • 68
Grapes
  • 2,473
  • 3
  • 26
  • 42
  • 1
    Don't be afraid of returning by value. Any compiler will do NVRO on it and construct the string in place at the callsite. – chris Apr 05 '13 at 04:49
  • 1
    Option A isn't an option. You'll get a compile error. – Vaughn Cato Apr 05 '13 at 04:53
  • @VaughnCato Not necessarily. It compiles, but the std::move has little effect. – Agentlien Apr 05 '13 at 04:56
  • @Agentlien: It doesn't compile for me on g++ 4.7.2: error: invalid initialization of non-const reference of type ‘std::basic_string&’ from an rvalue of type ‘std::string {aka std::basic_string}’, which seems right since you generally can't bind a non-const reference to temporary. – Vaughn Cato Apr 05 '13 at 04:57
  • @VaughnCato Ah, yes, I misread your comment. Thought you referred to the return itself, not the variable initialization. – Agentlien Apr 05 '13 at 05:01
  • @VaughnCato It seems to compile with fine with MS compiler (VS2012), but I agree that it's an invalid implementation (that's why I was worried that Option A would not be safe). Since it doesn't compile in GCC, I'll stick to Option B – Grapes Apr 05 '13 at 05:05

2 Answers2

3

The best way to do it would be to change your method to this:

std::string StringTest()
{
    std::string hello_world("Testing...");

    return hello_world;
}

There is no need to tell the function that the returned object should be an rvalue, this comes automatically from the value being a temporary - that is, it is not bound to any name at the call site. As such, the initialized will value already be move-constructed (unless the temporary inside the function is elided entirely), with no need for you to manually express such an intention.

Also, since your return type is std::string, you will return by value, not by rvalue-reference, so there is no point to moving the return argument before returning. In fact, calling std::move will have no effect other than possibly tricking your compiler to perform an additional unnecessary move-constructor before returning your value. Such a trick serves no purpose other than making your code slightly more complex and possibly slower to run.

You also do want to return by value. One reason is because of Return Value Optimization (RVO). Most compilers will do this, which means you're better off just returning a "copy" which gets elided than trying to be clever and returning a reference.

Even when RVO cannot be applied, returning it by rvalue-reference still does not change how it is returned. When the value is returned, since it is a temporary at the call site, the returned value will already be an rvalue which can be passed along by rvalue reference to whatever function needs it as an argument. For instance, if you use the return value to intialize a variable and the return value is not elided, the move constructor still be called if available. So, not much is lost by simply returning by value.

Given this, you should probably do one of these to catch the value:

auto string_test_output = StringTest();
std::string string_test_output = StringTest();
Agentlien
  • 4,996
  • 1
  • 16
  • 27
  • I was aware of RVO, but should I still add std::move(hello_world) to be on the safe side to make sure the compiler knows it's an rvalue reference? – Grapes Apr 05 '13 at 05:07
  • @Grapes No, definitely not. That doesn't add anything. The fact that it is an rvalue comes from it being a temporary at the call site. That is, it is returned from the function in which it is created, and is not bound to any name when the function exits. You making it into an rvalue inside the function, before returning, only means there's a risk of an extra, unnecessary, move constructor being called before it is returned. – Agentlien Apr 05 '13 at 05:09
  • I completely understand your reasoning for not having std::move. That being said, is there any times that I should NOT trust RVO and instead use std::move? – Grapes Apr 05 '13 at 05:18
  • Not exactly, no. RVO is used in most modern compilers (I don't know any which don't have it), and it usually works really well. The only times you should use std::move is when you, as a programmer, need to pass something which you know is an rvalue to a function. For example, when swapping members inside a move constructor. (As a sidenote: I compiled your example with Clang 3.2 and it did perform an extra move constructor call.) – Agentlien Apr 05 '13 at 05:24
  • Thanks for testing. I'll use your approach. – Grapes Apr 05 '13 at 05:27
  • 1
    @Grapes The answer is worded a bit misleading. As it stands it sounds like you should omit the move just because you can count on RVO here. But it leaves out the detail, that even if RVO doesn't happen, the string is still moved out, since in a `return` statement the `hello_world` is treated as an rvalue reference, since the compiler knows that it isn't needed anymore. And that is guaranteed by standard (in contrast to RVO). So yes, omit the `move`, but not just because of RVO, but because it is completely unneccessary by standard. see http://stackoverflow.com/q/14856344/743214 – Christian Rau Apr 05 '13 at 07:35
  • @Agentlien: there are cases where RVO is explicitly forbidden, but you still do not want to put function std::move() in return statement. See here: http://stackoverflow.com/a/9268254/838509 – Andrzej Apr 05 '13 at 08:09
  • @Andrzej You're right, of course, I didn't formulate my point very clearly in that regard, and only clarified in the comments. In lieu of your comment, I've updated my answer. Hope you find that better? – Agentlien Apr 05 '13 at 08:41
  • @ChristianRau I realized I only explained that detail in the comments. So, I added it to the answer. – Agentlien Apr 05 '13 at 08:50
  • @Agentlien Meh, for me this still doesn't make clear enough that *not* writing `move` *already performs return by move construction* (if any). Or it may be just me not being able to read this out of your answer. – Christian Rau Apr 05 '13 at 11:07
  • @ChristianRau I've made some more clarifications. Now I added that point to the very beginning of my post. Hope that does it for you. – Agentlien Apr 05 '13 at 11:18
  • @Agentlien But this whole paragraph seems to only speak about the *"returned value"* and the *"call site"*, therefore the value moved from the *internal return storage* into the variable taking it on the call site. It doesn't really speak about the value moved into that *internal return storage* at the point of the `return` statement. The point is that writing `std::move` inside of the `return` statement doesn't *"not achieve very much"*, it achieves downright nothing, because `hello_string` is already an rvalue in this statement. – Christian Rau Apr 05 '13 at 11:25
  • @ChristianRau I see what you mean, and I know what you're trying to say. The only effect that `std::move` has is triggering a potential extra move constructor before the return.. If you think you can express it better, please feel free to modify my answer. – Agentlien Apr 05 '13 at 11:26
  • At the point when the `return` statement is compiled, there isn't any *call-site* to be taken into account. `return local_variable` is *always* the same like `std::move(local_variable)` (except for the implications on RVO, of course). Doesn't have anything to do with the *call site* and its possible moving. – Christian Rau Apr 05 '13 at 11:28
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/27628/discussion-between-agentlien-and-christian-rau) – Agentlien Apr 05 '13 at 11:29
  • @Agentlien *"If you think you can express it better, please feel free to modify my answer."* - This hard to do, I have to admit. Even if the words are not that hard to find, it is hard to place this (in a didactically good way) between all the other statements about call-sites and RVO without deviating too much from the purpose of the answer. I'll just leave it the way it is and hope for the OP to have read some comment under this answer. The current answer is still good enough to not justify a down-vote, I think. – Christian Rau Apr 05 '13 at 11:31
  • @ChristianRau He has, and if you look at the first comments, I even seemed to get this point through to him before your first comment. So, I'd worry more about readers who find this later on. I'm not saying it's easy, I'm just saying I've tried and found that I cannot express it better than I have (without starting from scratch). – Agentlien Apr 05 '13 at 11:33
  • *"if you look at the first comments"* - They seem to only speak about the call-site either, but nevermind. *"So, I'd worry more about readers who find this later on"* - Me, too, but nevermind. The only thing I can suggest to you is not to speak about any call sites (when explaining the particular point we're discussing), because they don't have anything to do with it and always seem to lead you to the wrong side of the return operation (the side you have explained thoroughly enough multiple times already). – Christian Rau Apr 05 '13 at 11:39
0

Returning an std::string (and in general, any moveable type) by value and thus initializing a new object will never trigger copy construction. In the worse case it will trigger move construction, but usually due to copy elision, there will be no run-time overhead involved.

I recommend this article for background on value semantics and its costs.

Andrzej
  • 5,027
  • 27
  • 36