3

I have a function that "builds" a structure to return:

struct stuff {
    int a;
    double b;
    Foo c;
};
stuff generate_stuff() {
    Foo c = generate_foo();
    //do stuff to Foo, that changes Foo:
    //...
    return {1, 2.0, c};  //should this be return {1, 2.0, move(c)};?
}

Should I be moving c out of the function? I realize that frequently, (N)RVO can build the object in place, however there might be times when this ISN'T the case. When can't (N)RVO be done, and thus, when should I move an object of a function?

Put another way, this is obviously going to be RVO of the temporary that is returned. The question becomes, will NRVO (named return value optimization) happen with c? Will c be constructed in place (at the call site of the function, inside the temporary stuff structure), or will c be constructed in the function, and then copied into the structure at the call site.

Andrew Spott
  • 3,457
  • 8
  • 33
  • 59
  • This constructs a temporary of type `stuff`: it would hold a copy of `c` (being an lvalue, `c` can't be moved into this temporary). Then this temporary itself can be moved out of the function, and its `Foo` member (itself a copy of `c`) would be moved at that time. – Igor Tandetnik Mar 10 '15 at 00:05
  • If `Foo` contains builtin types then it's not going to make a difference, but assuming it is cheaper to move construct a `Foo` compared to copy constructing it, you should use `std::move(c)`. Copy elision allows copy/move of `stuff` be elided in that instance, but the same doesn't apply to the construction of the data members that constitute `stuff`. – Praetorian Mar 10 '15 at 00:05
  • RVO only applies to temporaries, and NRVO only applies in return-statements if the expression to be returned is a name. Hence, both `return {1, 2.0, generate_foo()};` and `stuff s = {1, 2.0, generate_foo()}; /* do something */ return s;` can use (N)RVO. – dyp Mar 10 '15 at 00:08
  • @Praetorian: Foo can be assumed to not be a POD. @IgorTandetnik: So, the temporary of type `stuff` will be constructed at the call site (RVO), the question is if `c` will be constructed in place, or will have to be moved. – Andrew Spott Mar 10 '15 at 00:09
  • possible duplicate of [c++11 Return value optimization or move?](http://stackoverflow.com/questions/17473753/c11-return-value-optimization-or-move) – Pradhan Mar 10 '15 at 00:10
  • @Pradhan: No. This is a combination of a possible NRVO inside a structure, and RVO of the structure. The linked thread is plain old NRVO – Andrew Spott Mar 10 '15 at 00:12
  • @AndrewSpott NRVO and RVO are how compilers implement optimizations. They aren't terms defined in the standard AFAIK. What the standard does define are cases which are eligible for copy elision. The cases where compilers do NRVO and RVO are subsets of the copies which can be elided. If a compiler does not perform copy elision on an eligible copy, it is *required* by the standard to move. This is what the answer explains and this covers all possibilities, doesn't it? – Pradhan Mar 10 '15 at 00:16
  • @AndrewSpott Ah, ok. After the edit, I see wht you mean. This, IIRC, was not correctly handled in C++11 but was modified in C++14. Let me try to find a question/standard quote. – Pradhan Mar 10 '15 at 00:18
  • @Pradhan: Thanks. Maybe C++14 should be added to the tags then (I didn't think this had changed – Andrew Spott Mar 10 '15 at 00:19
  • *"this is obviously going to be RVO of the temporary that is returned"* I don't think this is obvious, since I don't think this is true: `return {..}` will not produce a temporary that is copied/moved into the return value, it will initialize the return value *directly*. Hence there is no temporary. – dyp Mar 10 '15 at 00:36
  • @dyp: That is the return value optimization... instead of creating the temporary, the value is constructed in place. – Andrew Spott Mar 10 '15 at 00:38
  • No, that is no optimization. You can construct with `return {..};` types that do not have a copy nor a move constructor. See http://stackoverflow.com/q/7935639 – dyp Mar 10 '15 at 00:40
  • @dyp: Because the standard allows for copy elision. RVO is a special case of copy elision. It is allowed, but not required: thus it is an optimization. – Andrew Spott Mar 10 '15 at 00:42
  • 2
    No, it is not an optimization. The Standard mandates that `return {..};` directly initializes the return value. This is different from `return ` *expression* `;`, note that braced-init-lists are not expressions. – dyp Mar 10 '15 at 00:43

1 Answers1

3

What your call to move(c) does is not moving c out of the function, but moving it into the temporary struct that is returned from the function. The temporary return value should always benefit from RVO. However, I believe that the move/copy of c into the temporary cannot be optimized away, as c itself is not a temporary. So here the move version should always be at least as efficient as the copy version (Tested for a simple scenario with g++, clang++ and MVC++).

If you have to absolutely minimize the number of copy/move operations, then you could write

struct stuff {
    int a;
    double b;
    Foo c;
};
stuff generate_stuff() {
    stuff s{ 1, 2.0, generate_foo() };
    //use s.c instead of c
    //...
    return s;  
}

which would result in only a single construction of Foo and no copies/moves thanks to NRVO.

EDIT: As @dyp pointed out in the comments to your question, the in-place construction of Stuff isn't actually a case of RVO, but required by the standard. Anyway, the important part is that the move/copy of c cannot be elided so that using move should never result in a performance penalty.

MikeMB
  • 20,029
  • 9
  • 57
  • 102
  • @dyp: That is the exact opposite of what I said - or my understanding of the English language is really bad. – MikeMB Mar 10 '15 at 00:54